scirs2_sparse/linalg/eigen/
mod.rs1pub mod arnoldi;
7pub mod general;
8pub mod generalized;
9pub mod lanczos;
10pub mod power_iteration;
11pub mod symmetric;
12
13pub use arnoldi::{iram, iram_shift_invert, ArnoldiConfig};
15pub use general::eigs;
16pub use generalized::{eigsh_generalized, eigsh_generalized_enhanced};
17pub use lanczos::{lanczos, EigenResult, LanczosOptions};
18pub use power_iteration::{power_iteration, PowerIterationOptions};
19pub use symmetric::{eigsh, eigsh_shift_invert, eigsh_shift_invert_enhanced};
20
21#[derive(Debug, Clone)]
25pub struct ArpackOptions {
26 pub max_iter: usize,
27 pub tol: f64,
28}
29
30#[derive(Debug, Clone, Copy)]
31pub enum EigenvalueMethod {
32 Lanczos,
33 PowerIteration,
34 Arpack,
35}
36
37#[derive(Debug, Clone, Copy)]
38pub enum EigenvalueMode {
39 Normal,
40 ShiftInvert,
41 Buckling,
42}
43
44#[derive(Debug, Clone)]
46#[allow(dead_code)]
47pub struct EigenSolverConfig {
48 pub max_iter: usize,
50 pub tolerance: f64,
52 pub num_eigenvalues: usize,
54 pub compute_eigenvectors: bool,
56 pub which: String,
58}
59
60impl Default for EigenSolverConfig {
61 fn default() -> Self {
62 Self {
63 max_iter: 1000,
64 tolerance: 1e-8,
65 num_eigenvalues: 6,
66 compute_eigenvectors: true,
67 which: "LA".to_string(),
68 }
69 }
70}
71
72impl EigenSolverConfig {
73 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn with_max_iter(mut self, max_iter: usize) -> Self {
80 self.max_iter = max_iter;
81 self
82 }
83
84 pub fn with_tolerance(mut self, tolerance: f64) -> Self {
86 self.tolerance = tolerance;
87 self
88 }
89
90 pub fn with_num_eigenvalues(mut self, num_eigenvalues: usize) -> Self {
92 self.num_eigenvalues = num_eigenvalues;
93 self
94 }
95
96 pub fn with_compute_eigenvectors(mut self, compute_eigenvectors: bool) -> Self {
98 self.compute_eigenvectors = compute_eigenvectors;
99 self
100 }
101
102 pub fn with_which(mut self, which: &str) -> Self {
104 self.which = which.to_string();
105 self
106 }
107
108 pub fn to_lanczos_options(&self) -> LanczosOptions {
110 LanczosOptions {
111 max_iter: self.max_iter,
112 max_subspace_size: (self.num_eigenvalues * 2 + 10).max(20),
113 tol: self.tolerance,
114 numeigenvalues: self.num_eigenvalues,
115 compute_eigenvectors: self.compute_eigenvectors,
116 }
117 }
118
119 pub fn to_power_iteration_options(&self) -> PowerIterationOptions {
121 PowerIterationOptions {
122 max_iter: self.max_iter,
123 tol: self.tolerance,
124 normalize: true,
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
131pub enum EigenSolverStrategy {
132 PowerIteration,
134 Lanczos,
136 Symmetric,
138 ShiftInvert,
140 #[default]
142 Auto,
143}
144
145pub fn solve_eigenvalues<T>(
150 matrix: &crate::sym_csr::SymCsrMatrix<T>,
151 config: &EigenSolverConfig,
152 strategy: EigenSolverStrategy,
153) -> crate::error::SparseResult<EigenResult<T>>
154where
155 T: scirs2_core::numeric::Float
156 + scirs2_core::SparseElement
157 + std::fmt::Debug
158 + Copy
159 + std::ops::Add<Output = T>
160 + std::ops::Sub<Output = T>
161 + std::ops::Mul<Output = T>
162 + std::ops::Div<Output = T>
163 + std::iter::Sum
164 + scirs2_core::simd_ops::SimdUnifiedOps
165 + Send
166 + Sync
167 + 'static,
168{
169 let (n, _) = matrix.shape();
170
171 let selected_strategy = match strategy {
172 EigenSolverStrategy::Auto => {
173 if config.num_eigenvalues == 1 && config.which == "LA" {
175 EigenSolverStrategy::PowerIteration
176 } else if n < 1000 {
177 EigenSolverStrategy::Lanczos
178 } else {
179 EigenSolverStrategy::Symmetric
180 }
181 }
182 other => other,
183 };
184
185 match selected_strategy {
186 EigenSolverStrategy::PowerIteration => {
187 let opts = config.to_power_iteration_options();
188 let power_result = power_iteration::power_iteration(matrix, &opts, None)?;
189 Ok(EigenResult {
191 eigenvalues: power_result.eigenvalues,
192 eigenvectors: power_result.eigenvectors,
193 iterations: power_result.iterations,
194 residuals: power_result.residuals,
195 converged: power_result.converged,
196 })
197 }
198 EigenSolverStrategy::Lanczos => {
199 let opts = config.to_lanczos_options();
200 let result = lanczos::lanczos(matrix, &opts, None)?;
201 Ok(result)
202 }
203 EigenSolverStrategy::Symmetric => {
204 let opts = config.to_lanczos_options();
205 let result = symmetric::eigsh(
206 matrix,
207 Some(config.num_eigenvalues),
208 Some(&config.which),
209 Some(opts),
210 )?;
211 Ok(result)
212 }
213 EigenSolverStrategy::ShiftInvert => {
214 let opts = config.to_lanczos_options();
216 let result = symmetric::eigsh_shift_invert(
217 matrix,
218 T::sparse_zero(),
219 Some(config.num_eigenvalues),
220 Some(&config.which),
221 Some(opts),
222 )?;
223 Ok(result)
224 }
225 EigenSolverStrategy::Auto => {
226 unreachable!("Auto strategy should have been resolved")
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use crate::sym_csr::SymCsrMatrix;
236
237 #[test]
238 fn test_eigen_solver_config() {
239 let config = EigenSolverConfig::new()
240 .with_max_iter(500)
241 .with_tolerance(1e-10)
242 .with_num_eigenvalues(3)
243 .with_which("SA");
244
245 assert_eq!(config.max_iter, 500);
246 assert_eq!(config.tolerance, 1e-10);
247 assert_eq!(config.num_eigenvalues, 3);
248 assert_eq!(config.which, "SA");
249 }
250
251 #[test]
252 fn test_unified_solver_auto() {
253 let data = vec![2.0, 1.0, 2.0];
256 let indptr = vec![0, 1, 3];
257 let indices = vec![0, 0, 1];
258 let matrix = SymCsrMatrix::new(data, indptr, indices, (2, 2)).expect("Operation failed");
259
260 let config = EigenSolverConfig::new().with_num_eigenvalues(1);
261 let result = solve_eigenvalues(&matrix, &config, EigenSolverStrategy::Auto)
262 .expect("Operation failed");
263
264 assert!(result.converged);
265 assert_eq!(result.eigenvalues.len(), 1);
266 }
267
268 #[test]
269 fn test_unified_solver_power_iteration() {
270 let data = vec![3.0, 1.0, 2.0];
272 let indptr = vec![0, 1, 3];
273 let indices = vec![0, 0, 1];
274 let matrix = SymCsrMatrix::new(data, indptr, indices, (2, 2)).expect("Operation failed");
275
276 let config = EigenSolverConfig::new().with_num_eigenvalues(1);
277 let result = solve_eigenvalues(&matrix, &config, EigenSolverStrategy::PowerIteration)
278 .expect("Operation failed");
279
280 assert!(result.converged);
281 assert_eq!(result.eigenvalues.len(), 1);
282 }
283
284 #[test]
285 fn test_unified_solver_lanczos() {
286 let data = vec![4.0, 2.0, 3.0];
288 let indptr = vec![0, 1, 3];
289 let indices = vec![0, 0, 1];
290 let matrix = SymCsrMatrix::new(data, indptr, indices, (2, 2)).expect("Operation failed");
291
292 let config = EigenSolverConfig::new().with_num_eigenvalues(2);
293 let result = solve_eigenvalues(&matrix, &config, EigenSolverStrategy::Lanczos)
294 .expect("Operation failed");
295
296 assert!(result.converged);
297 assert!(!result.eigenvalues.is_empty());
298 }
299}