1mod chebyshev;
30mod graph_filter;
31mod wavelets;
32mod clustering;
33
34pub use chebyshev::{ChebyshevPolynomial, ChebyshevExpansion};
35pub use graph_filter::{SpectralFilter, FilterType, GraphFilter};
36pub use wavelets::{GraphWavelet, WaveletScale, SpectralWaveletTransform};
37pub use clustering::{SpectralClustering, ClusteringConfig};
38
39#[derive(Debug, Clone)]
42pub struct ScaledLaplacian {
43 pub entries: Vec<(usize, usize, f64)>,
45 pub n: usize,
47 pub lambda_max: f64,
49}
50
51impl ScaledLaplacian {
52 pub fn from_adjacency(adj: &[f64], n: usize) -> Self {
54 let mut degrees = vec![0.0; n];
56 for i in 0..n {
57 for j in 0..n {
58 degrees[i] += adj[i * n + j];
59 }
60 }
61
62 let mut entries = Vec::new();
64 for i in 0..n {
65 if degrees[i] > 0.0 {
67 entries.push((i, i, degrees[i]));
68 }
69 for j in 0..n {
71 if i != j && adj[i * n + j] != 0.0 {
72 entries.push((i, j, -adj[i * n + j]));
73 }
74 }
75 }
76
77 let lambda_max = Self::estimate_lambda_max(&entries, n, 20);
79
80 let scale = 2.0 / lambda_max;
82 let scaled_entries: Vec<(usize, usize, f64)> = entries
83 .iter()
84 .map(|&(i, j, v)| {
85 if i == j {
86 (i, j, scale * v - 1.0)
87 } else {
88 (i, j, scale * v)
89 }
90 })
91 .collect();
92
93 Self {
94 entries: scaled_entries,
95 n,
96 lambda_max,
97 }
98 }
99
100 pub fn from_sparse_adjacency(edges: &[(usize, usize, f64)], n: usize) -> Self {
102 let mut degrees = vec![0.0; n];
104 for &(i, j, w) in edges {
105 degrees[i] += w;
106 if i != j {
107 degrees[j] += w; }
109 }
110
111 let mut entries = Vec::new();
113 for i in 0..n {
114 if degrees[i] > 0.0 {
115 entries.push((i, i, degrees[i]));
116 }
117 }
118 for &(i, j, w) in edges {
119 if w != 0.0 {
120 entries.push((i, j, -w));
121 if i != j {
122 entries.push((j, i, -w));
123 }
124 }
125 }
126
127 let lambda_max = Self::estimate_lambda_max(&entries, n, 20);
128 let scale = 2.0 / lambda_max;
129
130 let scaled_entries: Vec<(usize, usize, f64)> = entries
131 .iter()
132 .map(|&(i, j, v)| {
133 if i == j {
134 (i, j, scale * v - 1.0)
135 } else {
136 (i, j, scale * v)
137 }
138 })
139 .collect();
140
141 Self {
142 entries: scaled_entries,
143 n,
144 lambda_max,
145 }
146 }
147
148 fn estimate_lambda_max(entries: &[(usize, usize, f64)], n: usize, iters: usize) -> f64 {
150 let mut x = vec![1.0 / (n as f64).sqrt(); n];
151 let mut lambda = 1.0;
152
153 for _ in 0..iters {
154 let mut y = vec![0.0; n];
156 for &(i, j, v) in entries {
157 y[i] += v * x[j];
158 }
159
160 let mut dot = 0.0;
162 let mut norm_sq = 0.0;
163 for i in 0..n {
164 dot += x[i] * y[i];
165 norm_sq += y[i] * y[i];
166 }
167 lambda = dot;
168
169 let norm = norm_sq.sqrt().max(1e-15);
171 for i in 0..n {
172 x[i] = y[i] / norm;
173 }
174 }
175
176 lambda.abs().max(1.0)
177 }
178
179 pub fn apply(&self, x: &[f64]) -> Vec<f64> {
181 let mut y = vec![0.0; self.n];
182 for &(i, j, v) in &self.entries {
183 if j < x.len() {
184 y[i] += v * x[j];
185 }
186 }
187 y
188 }
189
190 pub fn lambda_max(&self) -> f64 {
192 self.lambda_max
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq)]
198pub enum LaplacianNorm {
199 Unnormalized,
201 Symmetric,
203 RandomWalk,
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_scaled_laplacian() {
213 let adj = vec![
215 0.0, 1.0, 0.0,
216 1.0, 0.0, 1.0,
217 0.0, 1.0, 0.0,
218 ];
219
220 let laplacian = ScaledLaplacian::from_adjacency(&adj, 3);
221
222 assert_eq!(laplacian.n, 3);
223 assert!(laplacian.lambda_max > 0.0);
224
225 let x = vec![1.0, 0.0, -1.0];
227 let y = laplacian.apply(&x);
228 assert_eq!(y.len(), 3);
229 }
230
231 #[test]
232 fn test_sparse_laplacian() {
233 let edges = vec![(0, 1, 1.0), (1, 2, 1.0)];
235 let laplacian = ScaledLaplacian::from_sparse_adjacency(&edges, 3);
236
237 assert_eq!(laplacian.n, 3);
238 assert!(laplacian.lambda_max > 0.0);
239 }
240}