Skip to main content

god_graph/utils/
matrix.rs

1//! 矩阵表示模块
2//!
3//! 提供图的矩阵表示和线性代数集成
4//! 需要启用 `matrix` 特性
5
6use crate::graph::traits::{GraphBase, GraphQuery};
7use crate::graph::Graph;
8use crate::node::NodeIndex;
9use nalgebra::{DMatrix, DVector};
10
11/// 图的邻接矩阵表示
12pub struct AdjacencyMatrix {
13    matrix: DMatrix<f64>,
14    node_indices: Vec<NodeIndex>,
15}
16
17impl AdjacencyMatrix {
18    /// 从图构建邻接矩阵
19    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    /// 获取底层矩阵
52    pub fn as_matrix(&self) -> &DMatrix<f64> {
53        &self.matrix
54    }
55
56    /// 获取节点索引映射
57    pub fn node_indices(&self) -> &[NodeIndex] {
58        &self.node_indices
59    }
60
61    /// 获取矩阵大小
62    pub fn size(&self) -> usize {
63        self.matrix.nrows()
64    }
65
66    /// 获取节点在矩阵中的位置
67    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    /// 获取节点从矩阵位置
74    pub fn node_from_position(&self, pos: usize) -> Option<NodeIndex> {
75        self.node_indices.get(pos).copied()
76    }
77}
78
79/// 图的拉普拉斯矩阵表示
80///
81/// L = D - A,其中 D 是度矩阵,A 是邻接矩阵
82pub struct LaplacianMatrix {
83    matrix: DMatrix<f64>,
84    node_indices: Vec<NodeIndex>,
85}
86
87impl LaplacianMatrix {
88    /// 从图构建拉普拉斯矩阵
89    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        // 构建度矩阵和邻接矩阵
101        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                    // 非对角线元素为 -w_ij
109                    matrix[(i, j)] = -weight;
110                    // 对角线元素为度
111                    matrix[(i, i)] += weight;
112                    matrix[(j, j)] += weight;
113                }
114            }
115        }
116
117        Self {
118            matrix,
119            node_indices,
120        }
121    }
122
123    /// 获取底层矩阵
124    pub fn as_matrix(&self) -> &DMatrix<f64> {
125        &self.matrix
126    }
127
128    /// 获取节点索引映射
129    pub fn node_indices(&self) -> &[NodeIndex] {
130        &self.node_indices
131    }
132}
133
134/// 计算图的 Fiedler 向量(拉普拉斯矩阵的第二小特征值对应的特征向量)
135///
136/// Fiedler 向量可用于图分割和社区检测
137pub 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    // 使用幂迭代法计算最小非零特征值对应的特征向量
145    // 这里使用简化的实现
146    let n = matrix.nrows();
147    if n < 2 {
148        return None;
149    }
150
151    // 初始化随机向量
152    let mut v = DVector::from_fn(n, |i, _| (i as f64 * 0.1).sin());
153    v.normalize_mut();
154
155    // 减去与全 1 向量平行的分量(对应零特征值)
156    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    // 幂迭代
162    for _ in 0..100 {
163        let mut w = matrix * &v;
164
165        // 减去与全 1 向量平行的分量
166        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
179/// 计算图的谱半径(邻接矩阵的最大特征值的绝对值)
180pub 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    // 使用幂迭代法计算最大特征值
188    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        // 检查邻接矩阵元素
226        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        // 使用有向图测试,避免无向图双向边的问题
234        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        // 拉普拉斯矩阵的对角线元素应该是节点的度(出度 + 入度)
243        assert_eq!(lap.matrix[(0, 0)], 1.0); // A 的度为 1(只有出边)
244        assert_eq!(lap.matrix[(1, 1)], 2.0); // B 的度为 2(入边 + 出边)
245        assert_eq!(lap.matrix[(2, 2)], 1.0); // C 的度为 1(只有入边)
246
247        // 非对角线元素应该是 -1(如果有边)或 0
248        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        // 完全图 K3 的谱半径是 2(对于无向图)
255        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), // A-B
260                (1, 2, 1.0),
261                (2, 1, 1.0), // B-C
262                (2, 0, 1.0),
263                (0, 2, 1.0), // C-A
264            ])
265            .build()
266            .unwrap();
267
268        let radius = spectral_radius(&graph);
269        // 双向完全图的谱半径应该是 2
270        assert!((radius - 2.0).abs() < 0.5);
271    }
272}