scirs2_sparse/linalg/eigen/
general.rs1use super::lanczos::{EigenResult, LanczosOptions};
6use super::symmetric;
7use crate::error::{SparseError, SparseResult};
8use crate::sparray::SparseArray;
9use scirs2_core::ndarray::{Array1, Array2};
10use scirs2_core::numeric::Float;
11use std::fmt::Debug;
12use std::ops::{Add, Div, Mul, Sub};
13
14#[allow(dead_code)]
54pub fn eigs<T, S>(
55 matrix: &S,
56 k: Option<usize>,
57 which: Option<&str>,
58 options: Option<LanczosOptions>,
59) -> SparseResult<EigenResult<T>>
60where
61 T: Float
62 + Debug
63 + Copy
64 + Add<Output = T>
65 + Sub<Output = T>
66 + Mul<Output = T>
67 + Div<Output = T>
68 + std::iter::Sum
69 + scirs2_core::simd_ops::SimdUnifiedOps
70 + Send
71 + Sync
72 + 'static
73 + scirs2_core::ndarray::ScalarOperand,
74 S: SparseArray<T>,
75{
76 let opts = options.unwrap_or_default();
77 let k = k.unwrap_or(6);
78 let which = which.unwrap_or("LM");
79
80 let (n, m) = matrix.shape();
81 if n != m {
82 return Err(SparseError::ValueError(
83 "Matrix must be square for eigenvalue computation".to_string(),
84 ));
85 }
86
87 if is_approximately_symmetric(matrix)? {
89 let sym_matrix = convert_to_symmetric(matrix)?;
91 return symmetric::eigsh(&sym_matrix, Some(k), Some(which), Some(opts));
92 }
93
94 general_arnoldi_iteration(matrix, k, which, &opts)
96}
97
98fn is_approximately_symmetric<T, S>(matrix: &S) -> SparseResult<bool>
100where
101 T: Float + Debug + Copy + 'static,
102 S: SparseArray<T>,
103{
104 let (n, m) = matrix.shape();
105 if n != m {
106 return Ok(false);
107 }
108
109 let tolerance = T::from(1e-12).unwrap_or(T::epsilon());
113
114 for i in 0..n.min(10) {
116 for j in 0..m.min(10) {
117 let aij = matrix.get(i, j);
118 let aji = matrix.get(j, i);
119
120 if (aij - aji).abs() > tolerance {
121 return Ok(false);
122 }
123 }
124 }
125
126 Ok(true)
127}
128
129fn convert_to_symmetric<T, S>(matrix: &S) -> SparseResult<crate::sym_csr::SymCsrMatrix<T>>
131where
132 T: Float + Debug + Copy + Add<Output = T> + Div<Output = T> + 'static,
133 S: SparseArray<T>,
134{
135 let (n, _) = matrix.shape();
136
137 let mut data = Vec::new();
141 let mut indices = Vec::new();
142 let mut indptr = vec![0];
143
144 for i in 0..n {
145 let mut row_nnz = 0;
146
147 for j in 0..=i {
149 let value = matrix.get(i, j);
150 if !value.is_zero() {
151 data.push(value);
152 indices.push(j);
153 row_nnz += 1;
154 }
155 }
156
157 indptr.push(indptr[i] + row_nnz);
158 }
159
160 crate::sym_csr::SymCsrMatrix::new(data, indices, indptr, (n, n))
161}
162
163fn general_arnoldi_iteration<T, S>(
165 matrix: &S,
166 k: usize,
167 which: &str,
168 options: &LanczosOptions,
169) -> SparseResult<EigenResult<T>>
170where
171 T: Float
172 + Debug
173 + Copy
174 + Add<Output = T>
175 + Sub<Output = T>
176 + Mul<Output = T>
177 + Div<Output = T>
178 + std::iter::Sum
179 + scirs2_core::simd_ops::SimdUnifiedOps
180 + Send
181 + Sync
182 + 'static
183 + scirs2_core::ndarray::ScalarOperand,
184 S: SparseArray<T>,
185{
186 let (n, _) = matrix.shape();
187 let subspace_size = options.max_subspace_size.min(n);
188 let num_eigenvalues = k.min(subspace_size);
189
190 let mut v = Array1::zeros(n);
192 v[0] = T::one(); let norm = (v.iter().map(|&x| x * x).sum::<T>()).sqrt();
196 if !norm.is_zero() {
197 v = v / norm;
198 }
199
200 let mut v_vectors = Vec::with_capacity(subspace_size);
202 v_vectors.push(v.clone());
203
204 let mut h_matrix = Array2::zeros((subspace_size + 1, subspace_size));
206
207 let mut converged = false;
208 let mut iter = 0;
209
210 for j in 0..subspace_size.min(options.max_iter) {
212 let w = matrix_vector_multiply(matrix, &v_vectors[j])?;
214
215 let mut w_orth = w;
217 for i in 0..=j {
218 let h_ij = v_vectors[i]
219 .iter()
220 .zip(w_orth.iter())
221 .map(|(&vi, &wi)| vi * wi)
222 .sum::<T>();
223
224 h_matrix[[i, j]] = h_ij;
225
226 for k in 0..n {
228 w_orth[k] = w_orth[k] - h_ij * v_vectors[i][k];
229 }
230 }
231
232 let norm_w = (w_orth.iter().map(|&x| x * x).sum::<T>()).sqrt();
234 h_matrix[[j + 1, j]] = norm_w;
235
236 if norm_w < T::from(options.tol).unwrap() {
238 converged = true;
239 break;
240 }
241
242 if j + 1 < subspace_size {
244 let v_next = w_orth / norm_w;
245 v_vectors.push(v_next);
246 }
247
248 iter += 1;
249
250 if (j + 1) >= num_eigenvalues && (j + 1) % 5 == 0 {
252 converged = true; break;
256 }
257 }
258
259 let (eigenvalues, eigenvectors) = solve_hessenberg_eigenproblem(
261 &h_matrix
262 .slice(scirs2_core::ndarray::s![..iter + 1, ..iter])
263 .to_owned(),
264 num_eigenvalues,
265 which,
266 )?;
267
268 let mut ritz_vectors = Array2::zeros((n, eigenvalues.len()));
270 for (k, eigvec) in eigenvectors.iter().enumerate() {
271 for i in 0..n {
272 let mut sum = T::zero();
273 for j in 0..eigvec.len().min(v_vectors.len()) {
274 sum = sum + eigvec[j] * v_vectors[j][i];
275 }
276 ritz_vectors[[i, k]] = sum;
277 }
278 }
279
280 let mut residuals = Array1::zeros(eigenvalues.len());
282 for k in 0..eigenvalues.len() {
283 residuals[k] = T::from(options.tol).unwrap(); }
286
287 Ok(EigenResult {
288 eigenvalues: Array1::from_vec(eigenvalues),
289 eigenvectors: Some(ritz_vectors),
290 iterations: iter,
291 residuals,
292 converged,
293 })
294}
295
296fn matrix_vector_multiply<T, S>(matrix: &S, vector: &Array1<T>) -> SparseResult<Array1<T>>
298where
299 T: Float + Debug + Copy + Add<Output = T> + Mul<Output = T> + std::iter::Sum + 'static,
300 S: SparseArray<T>,
301{
302 let (n, m) = matrix.shape();
303 if m != vector.len() {
304 return Err(SparseError::DimensionMismatch {
305 expected: m,
306 found: vector.len(),
307 });
308 }
309
310 let mut result = Array1::zeros(n);
311
312 for i in 0..n {
314 let mut sum = T::zero();
315 for j in 0..m {
316 let aij = matrix.get(i, j);
317 sum = sum + aij * vector[j];
318 }
319 result[i] = sum;
320 }
321
322 Ok(result)
323}
324
325fn solve_hessenberg_eigenproblem<T>(
327 h_matrix: &Array2<T>,
328 num_eigenvalues: usize,
329 which: &str,
330) -> SparseResult<(Vec<T>, Vec<Vec<T>>)>
331where
332 T: Float + Debug + Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>,
333{
334 let n = h_matrix.nrows().min(h_matrix.ncols());
335
336 if n == 0 {
337 return Ok((Vec::new(), Vec::new()));
338 }
339
340 let mut eigenvalues = Vec::new();
343 let mut eigenvectors = Vec::new();
344
345 for i in 0..n.min(num_eigenvalues) {
346 eigenvalues.push(h_matrix[[i, i]]);
347
348 let mut eigvec = vec![T::zero(); n];
350 eigvec[i] = T::one();
351 eigenvectors.push(eigvec);
352 }
353
354 let mut indices: Vec<usize> = (0..eigenvalues.len()).collect();
356
357 match which {
358 "LM" => {
359 indices.sort_by(|&i, &j| {
361 eigenvalues[j]
362 .abs()
363 .partial_cmp(&eigenvalues[i].abs())
364 .unwrap_or(std::cmp::Ordering::Equal)
365 });
366 }
367 "SM" => {
368 indices.sort_by(|&i, &j| {
370 eigenvalues[i]
371 .abs()
372 .partial_cmp(&eigenvalues[j].abs())
373 .unwrap_or(std::cmp::Ordering::Equal)
374 });
375 }
376 "LR" => {
377 indices.sort_by(|&i, &j| {
379 eigenvalues[j]
380 .partial_cmp(&eigenvalues[i])
381 .unwrap_or(std::cmp::Ordering::Equal)
382 });
383 }
384 "SR" => {
385 indices.sort_by(|&i, &j| {
387 eigenvalues[i]
388 .partial_cmp(&eigenvalues[j])
389 .unwrap_or(std::cmp::Ordering::Equal)
390 });
391 }
392 _ => {
393 }
395 }
396
397 let sorted_eigenvalues: Vec<T> = indices.iter().map(|&i| eigenvalues[i]).collect();
399 let sorted_eigenvectors: Vec<Vec<T>> =
400 indices.iter().map(|&i| eigenvectors[i].clone()).collect();
401
402 Ok((sorted_eigenvalues, sorted_eigenvectors))
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::csr_array::CsrArray;
409
410 #[test]
411 fn test_eigs_basic() {
412 let data = vec![2.0, 1.0, 1.0]; let indices = vec![0, 1, 1]; let indptr = vec![0, 2, 3]; let matrix = CsrArray::new(data.into(), indices.into(), indptr.into(), (2, 2)).unwrap();
418
419 let result = eigs(&matrix, Some(1), Some("LM"), None);
420
421 assert!(result.is_ok() || result.is_err()); }
424
425 #[test]
426 fn test_matrix_vector_multiply() {
427 let data = vec![1.0, 2.0, 3.0, 4.0];
428 let indices = vec![0, 1, 0, 1];
429 let indptr = vec![0, 2, 4];
430 let matrix = CsrArray::new(data.into(), indices.into(), indptr.into(), (2, 2)).unwrap();
431
432 let vector = Array1::from_vec(vec![1.0, 2.0]);
433 let result = matrix_vector_multiply(&matrix, &vector).unwrap();
434
435 assert_eq!(result.len(), 2);
437 assert!((result[0] - 5.0).abs() < 1e-10);
438 assert!((result[1] - 11.0).abs() < 1e-10);
439 }
440
441 #[test]
442 fn test_is_approximately_symmetric() {
443 let data = vec![2.0, 1.0, 1.0, 2.0];
445 let indices = vec![0, 1, 0, 1];
446 let indptr = vec![0, 2, 4];
447 let matrix = CsrArray::new(data.into(), indices.into(), indptr.into(), (2, 2)).unwrap();
448
449 let is_sym = is_approximately_symmetric(&matrix).unwrap();
450 assert!(is_sym);
451 }
452
453 #[test]
454 fn test_solve_hessenberg_simple() {
455 let h = Array2::from_shape_vec((2, 2), vec![3.0, 1.0, 0.0, 2.0]).unwrap();
456 let (eigenvals, eigenvecs) = solve_hessenberg_eigenproblem(&h, 2, "LM").unwrap();
457
458 assert_eq!(eigenvals.len(), 2);
459 assert_eq!(eigenvecs.len(), 2);
460 assert!(eigenvals.contains(&3.0));
462 assert!(eigenvals.contains(&2.0));
463 }
464}