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