ringkernel_graph/algorithms/
spmv.rs1use crate::models::CsrMatrix;
7use crate::{GraphError, Result};
8
9#[derive(Debug, Clone)]
11pub struct SpmvConfig {
12 pub parallel: bool,
14 pub alpha: f64,
16}
17
18impl Default for SpmvConfig {
19 fn default() -> Self {
20 Self {
21 parallel: false,
22 alpha: 1.0,
23 }
24 }
25}
26
27impl SpmvConfig {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn parallel(mut self) -> Self {
35 self.parallel = true;
36 self
37 }
38
39 pub fn with_alpha(mut self, alpha: f64) -> Self {
41 self.alpha = alpha;
42 self
43 }
44}
45
46pub fn spmv(matrix: &CsrMatrix, x: &[f64]) -> Result<Vec<f64>> {
57 spmv_with_config(matrix, x, &SpmvConfig::default())
58}
59
60pub fn spmv_with_config(matrix: &CsrMatrix, x: &[f64], config: &SpmvConfig) -> Result<Vec<f64>> {
62 if x.len() != matrix.num_cols {
63 return Err(GraphError::DimensionMismatch {
64 expected: matrix.num_cols,
65 actual: x.len(),
66 });
67 }
68
69 if config.parallel {
70 spmv_parallel_impl(matrix, x, config.alpha)
71 } else {
72 spmv_sequential_impl(matrix, x, config.alpha)
73 }
74}
75
76fn spmv_sequential_impl(matrix: &CsrMatrix, x: &[f64], alpha: f64) -> Result<Vec<f64>> {
78 let mut y = vec![0.0; matrix.num_rows];
79
80 for (row, y_row) in y.iter_mut().enumerate() {
81 let start = matrix.row_ptr[row] as usize;
82 let end = matrix.row_ptr[row + 1] as usize;
83
84 let mut sum = 0.0;
85 for i in start..end {
86 let col = matrix.col_idx[i] as usize;
87 let val = matrix.values.as_ref().map(|v| v[i]).unwrap_or(1.0);
88 sum += val * x[col];
89 }
90
91 *y_row = alpha * sum;
92 }
93
94 Ok(y)
95}
96
97pub fn spmv_parallel(matrix: &CsrMatrix, x: &[f64]) -> Result<Vec<f64>> {
101 spmv_with_config(matrix, x, &SpmvConfig::default().parallel())
102}
103
104fn spmv_parallel_impl(matrix: &CsrMatrix, x: &[f64], alpha: f64) -> Result<Vec<f64>> {
106 let y: Vec<f64> = (0..matrix.num_rows)
108 .map(|row| {
109 let start = matrix.row_ptr[row] as usize;
110 let end = matrix.row_ptr[row + 1] as usize;
111
112 let mut sum = 0.0;
113 for i in start..end {
114 let col = matrix.col_idx[i] as usize;
115 let val = matrix.values.as_ref().map(|v| v[i]).unwrap_or(1.0);
116 sum += val * x[col];
117 }
118
119 alpha * sum
120 })
121 .collect();
122
123 Ok(y)
124}
125
126pub fn spmv_axpby(
130 matrix: &CsrMatrix,
131 x: &[f64],
132 y: &mut [f64],
133 alpha: f64,
134 beta: f64,
135) -> Result<()> {
136 if x.len() != matrix.num_cols {
137 return Err(GraphError::DimensionMismatch {
138 expected: matrix.num_cols,
139 actual: x.len(),
140 });
141 }
142
143 if y.len() != matrix.num_rows {
144 return Err(GraphError::DimensionMismatch {
145 expected: matrix.num_rows,
146 actual: y.len(),
147 });
148 }
149
150 for (row, y_row) in y.iter_mut().enumerate() {
151 let start = matrix.row_ptr[row] as usize;
152 let end = matrix.row_ptr[row + 1] as usize;
153
154 let mut sum = 0.0;
155 for i in start..end {
156 let col = matrix.col_idx[i] as usize;
157 let val = matrix.values.as_ref().map(|v| v[i]).unwrap_or(1.0);
158 sum += val * x[col];
159 }
160
161 *y_row = alpha * sum + beta * *y_row;
162 }
163
164 Ok(())
165}
166
167pub fn dot(x: &[f64], y: &[f64]) -> f64 {
169 x.iter().zip(y.iter()).map(|(&a, &b)| a * b).sum()
170}
171
172pub fn norm2(x: &[f64]) -> f64 {
174 dot(x, x).sqrt()
175}
176
177pub fn normalize(x: &mut [f64]) {
179 let n = norm2(x);
180 if n > 1e-10 {
181 for xi in x.iter_mut() {
182 *xi /= n;
183 }
184 }
185}
186
187pub fn power_iteration(
192 matrix: &CsrMatrix,
193 max_iterations: usize,
194 tolerance: f64,
195) -> Result<(Vec<f64>, f64)> {
196 if matrix.num_rows == 0 {
197 return Err(GraphError::EmptyGraph);
198 }
199
200 let n = matrix.num_rows;
202 let mut x = vec![1.0 / (n as f64).sqrt(); n];
203 let mut eigenvalue = 0.0;
204
205 for _ in 0..max_iterations {
206 let y = spmv(matrix, &x)?;
208
209 let new_eigenvalue = dot(&x, &y);
211
212 let norm = norm2(&y);
214 if norm < 1e-10 {
215 break;
216 }
217
218 x = y.into_iter().map(|yi| yi / norm).collect();
219
220 if (new_eigenvalue - eigenvalue).abs() < tolerance {
222 return Ok((x, new_eigenvalue));
223 }
224
225 eigenvalue = new_eigenvalue;
226 }
227
228 Ok((x, eigenvalue))
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_spmv_identity() {
237 let edges = [(0, 0, 1.0), (1, 1, 1.0), (2, 2, 1.0)];
239 let matrix = CsrMatrix::from_weighted_edges(3, &edges);
240
241 let x = vec![1.0, 2.0, 3.0];
242 let y = spmv(&matrix, &x).unwrap();
243
244 assert!((y[0] - 1.0).abs() < 1e-10);
246 assert!((y[1] - 2.0).abs() < 1e-10);
247 assert!((y[2] - 3.0).abs() < 1e-10);
248 }
249
250 #[test]
251 fn test_spmv_unweighted() {
252 let edges = [(0, 1), (0, 2), (1, 2)];
254 let matrix = CsrMatrix::from_edges(3, &edges);
255
256 let x = vec![1.0, 1.0, 1.0];
264 let y = spmv(&matrix, &x).unwrap();
265
266 assert!((y[0] - 2.0).abs() < 1e-10);
267 assert!((y[1] - 1.0).abs() < 1e-10);
268 assert!((y[2] - 0.0).abs() < 1e-10);
269 }
270
271 #[test]
272 fn test_spmv_weighted() {
273 let edges = [(0, 1, 2.0), (0, 2, 3.0), (1, 2, 4.0)];
274 let matrix = CsrMatrix::from_weighted_edges(3, &edges);
275
276 let x = vec![1.0, 1.0, 1.0];
277 let y = spmv(&matrix, &x).unwrap();
278
279 assert!((y[0] - 5.0).abs() < 1e-10);
284 assert!((y[1] - 4.0).abs() < 1e-10);
285 assert!((y[2] - 0.0).abs() < 1e-10);
286 }
287
288 #[test]
289 fn test_spmv_alpha() {
290 let edges = [(0, 1, 1.0)];
291 let matrix = CsrMatrix::from_weighted_edges(2, &edges);
292
293 let x = vec![1.0, 2.0];
294 let config = SpmvConfig::new().with_alpha(0.5);
295 let y = spmv_with_config(&matrix, &x, &config).unwrap();
296
297 assert!((y[0] - 1.0).abs() < 1e-10);
299 }
300
301 #[test]
302 fn test_spmv_dimension_mismatch() {
303 let matrix = CsrMatrix::from_edges(3, &[(0, 1)]);
304 let x = vec![1.0, 2.0]; let result = spmv(&matrix, &x);
307 assert!(matches!(result, Err(GraphError::DimensionMismatch { .. })));
308 }
309
310 #[test]
311 fn test_spmv_parallel_same_as_sequential() {
312 let edges = [(0, 1, 1.0), (0, 2, 2.0), (1, 2, 3.0), (2, 0, 4.0)];
313 let matrix = CsrMatrix::from_weighted_edges(3, &edges);
314
315 let x = vec![1.0, 2.0, 3.0];
316
317 let seq = spmv(&matrix, &x).unwrap();
318 let par = spmv_parallel(&matrix, &x).unwrap();
319
320 for i in 0..3 {
321 assert!((seq[i] - par[i]).abs() < 1e-10);
322 }
323 }
324
325 #[test]
326 fn test_spmv_axpby() {
327 let edges = [(0, 1, 1.0)];
328 let matrix = CsrMatrix::from_weighted_edges(2, &edges);
329
330 let x = vec![1.0, 2.0];
331 let mut y = vec![1.0, 1.0];
332
333 spmv_axpby(&matrix, &x, &mut y, 2.0, 3.0).unwrap();
335
336 assert!((y[0] - 7.0).abs() < 1e-10);
339 assert!((y[1] - 3.0).abs() < 1e-10);
340 }
341
342 #[test]
343 fn test_dot_product() {
344 let x = vec![1.0, 2.0, 3.0];
345 let y = vec![4.0, 5.0, 6.0];
346
347 assert!((dot(&x, &y) - 32.0).abs() < 1e-10);
348 }
349
350 #[test]
351 fn test_norm2() {
352 let x = vec![3.0, 4.0];
353 assert!((norm2(&x) - 5.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn test_normalize() {
358 let mut x = vec![3.0, 4.0];
359 normalize(&mut x);
360
361 assert!((norm2(&x) - 1.0).abs() < 1e-10);
362 assert!((x[0] - 0.6).abs() < 1e-10);
363 assert!((x[1] - 0.8).abs() < 1e-10);
364 }
365
366 #[test]
367 fn test_power_iteration() {
368 let mut builder = crate::models::CsrMatrixBuilder::new(2);
374 builder.add_weighted_edge(0, 0, 2.0);
375 builder.add_weighted_edge(0, 1, 1.0);
376 builder.add_weighted_edge(1, 0, 1.0);
377 builder.add_weighted_edge(1, 1, 2.0);
378 let matrix = builder.build();
379
380 let (eigenvector, eigenvalue) = power_iteration(&matrix, 100, 1e-6).unwrap();
381
382 assert!((eigenvalue - 3.0).abs() < 0.01);
384
385 let expected = 1.0 / 2.0_f64.sqrt();
387 assert!((eigenvector[0].abs() - expected).abs() < 0.01);
388 assert!((eigenvector[1].abs() - expected).abs() < 0.01);
389 }
390}