scirs2_sparse/linalg/
preconditioners.rs1use crate::csr::CsrMatrix;
4use crate::error::{SparseError, SparseResult};
5use crate::linalg::interface::LinearOperator;
6use scirs2_core::numeric::{Float, NumAssign, SparseElement};
7use std::fmt::Debug;
8use std::iter::Sum;
9
10pub struct JacobiPreconditioner<F> {
15 inv_diagonal: Vec<F>,
16 size: usize,
17}
18
19impl<F: Float + SparseElement> JacobiPreconditioner<F> {
20 pub fn new(matrix: &CsrMatrix<F>) -> SparseResult<Self>
22 where
23 F: Debug,
24 {
25 let n = matrix.rows();
26 let mut inv_diagonal = vec![F::sparse_zero(); n];
27
28 for (i, diag_inv) in inv_diagonal.iter_mut().enumerate().take(n) {
30 let diag_val = matrix.get(i, i);
31 if diag_val.abs() < F::epsilon() {
32 return Err(SparseError::ValueError(format!(
33 "Zero diagonal element at position {i}"
34 )));
35 }
36 *diag_inv = F::sparse_one() / diag_val;
37 }
38
39 Ok(Self {
40 inv_diagonal,
41 size: n,
42 })
43 }
44
45 pub fn from_diagonal(diagonal: Vec<F>) -> SparseResult<Self> {
47 let size = diagonal.len();
48 let mut inv_diagonal = vec![F::sparse_zero(); size];
49
50 for (i, &d) in diagonal.iter().enumerate() {
51 if d.abs() < F::epsilon() {
52 return Err(SparseError::ValueError(format!(
53 "Zero _diagonal element at position {i}"
54 )));
55 }
56 inv_diagonal[i] = F::sparse_one() / d;
57 }
58
59 Ok(Self { inv_diagonal, size })
60 }
61}
62
63impl<F: Float + NumAssign + SparseElement> LinearOperator<F> for JacobiPreconditioner<F> {
64 fn shape(&self) -> (usize, usize) {
65 (self.size, self.size)
66 }
67
68 fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
69 if x.len() != self.size {
70 return Err(SparseError::DimensionMismatch {
71 expected: self.size,
72 found: x.len(),
73 });
74 }
75
76 Ok(x.iter()
77 .zip(&self.inv_diagonal)
78 .map(|(&xi, &di)| xi * di)
79 .collect())
80 }
81
82 fn has_adjoint(&self) -> bool {
83 true
84 }
85
86 fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
87 self.matvec(x)
89 }
90}
91
92pub struct SSORPreconditioner<F> {
97 matrix: CsrMatrix<F>,
98 omega: F,
99 diagonal: Vec<F>,
100}
101
102impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> SSORPreconditioner<F> {
103 pub fn new(matrix: CsrMatrix<F>, omega: F) -> SparseResult<Self> {
105 if omega <= F::sparse_zero() || omega >= F::from(2.0).unwrap() {
106 return Err(SparseError::ValueError(
107 "Relaxation parameter omega must be in (0, 2)".to_string(),
108 ));
109 }
110
111 let n = matrix.rows();
112 let mut diagonal = vec![F::sparse_zero(); n];
113
114 for (i, diag) in diagonal.iter_mut().enumerate().take(n) {
116 *diag = matrix.get(i, i);
117 if diag.abs() < F::epsilon() {
118 return Err(SparseError::ValueError(format!(
119 "Zero diagonal element at position {i}"
120 )));
121 }
122 }
123
124 Ok(Self {
125 matrix,
126 omega,
127 diagonal,
128 })
129 }
130}
131
132impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> LinearOperator<F>
133 for SSORPreconditioner<F>
134{
135 fn shape(&self) -> (usize, usize) {
136 self.matrix.shape()
137 }
138
139 fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
140 let n = self.matrix.rows();
141 if x.len() != n {
142 return Err(SparseError::DimensionMismatch {
143 expected: n,
144 found: x.len(),
145 });
146 }
147
148 let mut y = vec![F::sparse_zero(); n];
150 for i in 0..n {
151 let mut sum = x[i];
152 let row_range = self.matrix.row_range(i);
153 let row_indices = &self.matrix.indices[row_range.clone()];
154 let row_data = &self.matrix.data[row_range];
155
156 for (idx, &j) in row_indices.iter().enumerate() {
157 if j < i {
158 sum -= self.omega * row_data[idx] * y[j];
159 }
160 }
161 y[i] = sum / self.diagonal[i];
162 }
163
164 let mut z = vec![F::sparse_zero(); n];
166 for i in 0..n {
167 z[i] = y[i] * self.diagonal[i];
168 }
169
170 let mut w = vec![F::sparse_zero(); n];
172 for i in (0..n).rev() {
173 let mut sum = z[i];
174 let row_range = self.matrix.row_range(i);
175 let row_indices = &self.matrix.indices[row_range.clone()];
176 let row_data = &self.matrix.data[row_range];
177
178 for (idx, &j) in row_indices.iter().enumerate() {
179 if j > i {
180 sum -= self.omega * row_data[idx] * w[j];
181 }
182 }
183 w[i] = sum / self.diagonal[i];
184 }
185
186 Ok(w)
187 }
188
189 fn has_adjoint(&self) -> bool {
190 false }
192}
193
194pub struct ILU0Preconditioner<F> {
199 l_data: Vec<F>,
200 u_data: Vec<F>,
201 l_indices: Vec<usize>,
202 u_indices: Vec<usize>,
203 l_indptr: Vec<usize>,
204 u_indptr: Vec<usize>,
205 n: usize,
206}
207
208impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> ILU0Preconditioner<F> {
209 pub fn new(matrix: &CsrMatrix<F>) -> SparseResult<Self> {
211 let n = matrix.rows();
212
213 let mut data = matrix.data.clone();
215 let indices = matrix.indices.clone();
216 let indptr = matrix.indptr.clone();
217
218 for k in 0..n {
220 let k_diag_idx = find_diagonal_index(&indices, &indptr, k)?;
221 let k_diag = data[k_diag_idx];
222
223 if k_diag.abs() < F::epsilon() {
224 return Err(SparseError::ValueError(format!(
225 "Zero pivot at position {k}"
226 )));
227 }
228
229 for i in (k + 1)..n {
231 let row_start = indptr[i];
232 let row_end = indptr[i + 1];
233
234 let mut k_pos = None;
236 for (&col, j) in indices[row_start..row_end].iter().zip(row_start..row_end) {
237 if col == k {
238 k_pos = Some(j);
239 break;
240 }
241 if col > k {
242 break;
243 }
244 }
245
246 if let Some(ki_idx) = k_pos {
247 let mult = data[ki_idx] / k_diag;
249 data[ki_idx] = mult;
250
251 let k_row_start = indptr[k];
253 let k_row_end = indptr[k + 1];
254
255 for kj_idx in k_row_start..k_row_end {
256 let j = indices[kj_idx];
257 if j <= k {
258 continue;
259 }
260
261 for ij_idx in row_start..row_end {
263 if indices[ij_idx] == j {
264 data[ij_idx] = data[ij_idx] - mult * data[kj_idx];
265 break;
266 }
267 }
268 }
269 }
270 }
271 }
272
273 let mut l_data = Vec::new();
275 let mut u_data = Vec::new();
276 let mut l_indices = Vec::new();
277 let mut u_indices = Vec::new();
278 let mut l_indptr = vec![0];
279 let mut u_indptr = vec![0];
280
281 for i in 0..n {
282 let row_start = indptr[i];
283 let row_end = indptr[i + 1];
284
285 for j in row_start..row_end {
286 let col = indices[j];
287 let val = data[j];
288
289 match col.cmp(&i) {
290 std::cmp::Ordering::Less => {
291 l_indices.push(col);
293 l_data.push(val);
294 }
295 std::cmp::Ordering::Equal => {
296 u_indices.push(col);
298 u_data.push(val);
299 }
300 std::cmp::Ordering::Greater => {
301 u_indices.push(col);
303 u_data.push(val);
304 }
305 }
306 }
307
308 l_indptr.push(l_indices.len());
309 u_indptr.push(u_indices.len());
310 }
311
312 Ok(Self {
313 l_data,
314 u_data,
315 l_indices,
316 u_indices,
317 l_indptr,
318 u_indptr,
319 n,
320 })
321 }
322}
323
324impl<F: Float + NumAssign + Sum + Debug + SparseElement + 'static> LinearOperator<F>
325 for ILU0Preconditioner<F>
326{
327 fn shape(&self) -> (usize, usize) {
328 (self.n, self.n)
329 }
330
331 fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
332 if x.len() != self.n {
333 return Err(SparseError::DimensionMismatch {
334 expected: self.n,
335 found: x.len(),
336 });
337 }
338
339 let mut y = vec![F::sparse_zero(); self.n];
341 for i in 0..self.n {
342 y[i] = x[i];
343 let row_start = self.l_indptr[i];
344 let row_end = self.l_indptr[i + 1];
345
346 for j in row_start..row_end {
347 let col = self.l_indices[j];
348 y[i] = y[i] - self.l_data[j] * y[col];
349 }
350 }
351
352 let mut z = vec![F::sparse_zero(); self.n];
354 for i in (0..self.n).rev() {
355 z[i] = y[i];
356 let row_start = self.u_indptr[i];
357 let row_end = self.u_indptr[i + 1];
358
359 let mut diag_val = F::sparse_one();
360 for j in row_start..row_end {
361 let col = self.u_indices[j];
362 match col.cmp(&i) {
363 std::cmp::Ordering::Equal => diag_val = self.u_data[j],
364 std::cmp::Ordering::Greater => z[i] = z[i] - self.u_data[j] * z[col],
365 std::cmp::Ordering::Less => {}
366 }
367 }
368
369 z[i] /= diag_val;
370 }
371
372 Ok(z)
373 }
374
375 fn has_adjoint(&self) -> bool {
376 false }
378}
379
380#[allow(dead_code)]
382fn find_diagonal_index(indices: &[usize], indptr: &[usize], row: usize) -> SparseResult<usize> {
383 let row_start = indptr[row];
384 let row_end = indptr[row + 1];
385
386 for (idx, &col) in indices[row_start..row_end]
387 .iter()
388 .enumerate()
389 .map(|(i, col)| (i + row_start, col))
390 {
391 if col == row {
392 return Ok(idx);
393 }
394 }
395
396 Err(SparseError::ValueError(format!(
397 "Missing diagonal element at row {row}"
398 )))
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn test_jacobi_preconditioner() {
407 let diagonal = vec![2.0, 3.0, 4.0];
409 let precond = JacobiPreconditioner::from_diagonal(diagonal).unwrap();
410
411 let x = vec![2.0, 6.0, 12.0];
413 let result = precond.matvec(&x).unwrap();
414
415 assert!((result[0] - 1.0).abs() < 1e-10);
417 assert!((result[1] - 2.0).abs() < 1e-10);
418 assert!((result[2] - 3.0).abs() < 1e-10);
419 }
420}