solvers/preconditioners/
ilu.rs1use crate::sparse::CsrMatrix;
7use crate::traits::{ComplexField, Preconditioner};
8use ndarray::Array1;
9use num_traits::FromPrimitive;
10
11#[derive(Debug, Clone)]
16pub struct IluPreconditioner<T: ComplexField> {
17 l_values: Vec<T>,
19 l_col_indices: Vec<usize>,
20 l_row_ptrs: Vec<usize>,
21 u_values: Vec<T>,
23 u_col_indices: Vec<usize>,
24 u_row_ptrs: Vec<usize>,
25 u_diag: Vec<T>,
27 n: usize,
29}
30
31impl<T: ComplexField> IluPreconditioner<T> {
32 pub fn from_csr(matrix: &CsrMatrix<T>) -> Self {
34 let n = matrix.num_rows;
35
36 let mut values = matrix.values.clone();
42 let col_indices = matrix.col_indices.clone();
43 let row_ptrs = matrix.row_ptrs.clone();
44
45 for i in 0..n {
47 for idx in row_ptrs[i]..row_ptrs[i + 1] {
51 let k = col_indices[idx];
52 if k >= i {
53 break;
54 }
55
56 let mut u_kk = T::zero();
58 for k_idx in row_ptrs[k]..row_ptrs[k + 1] {
59 if col_indices[k_idx] == k {
60 u_kk = values[k_idx];
61 break;
62 }
63 }
64
65 if u_kk.norm() < T::Real::from_f64(1e-30).unwrap() {
66 continue;
67 }
68
69 let l_ik = values[idx] * u_kk.inv();
71 values[idx] = l_ik;
72
73 for j_idx in row_ptrs[i]..row_ptrs[i + 1] {
75 let j = col_indices[j_idx];
76 if j <= k {
77 continue;
78 }
79
80 for k_j_idx in row_ptrs[k]..row_ptrs[k + 1] {
82 if col_indices[k_j_idx] == j {
83 values[j_idx] = values[j_idx] - l_ik * values[k_j_idx];
84 break;
85 }
86 }
87 }
88 }
89 }
90
91 let mut l_values = Vec::new();
93 let mut l_col_indices = Vec::new();
94 let mut l_row_ptrs = vec![0];
95
96 let mut u_values = Vec::new();
97 let mut u_col_indices = Vec::new();
98 let mut u_row_ptrs = vec![0];
99 let mut u_diag = vec![T::one(); n];
100
101 for i in 0..n {
102 for idx in row_ptrs[i]..row_ptrs[i + 1] {
103 let j = col_indices[idx];
104 let val = values[idx];
105
106 if j < i {
107 l_values.push(val);
109 l_col_indices.push(j);
110 } else {
111 u_values.push(val);
113 u_col_indices.push(j);
114 if j == i {
115 u_diag[i] = val;
116 }
117 }
118 }
119 l_row_ptrs.push(l_values.len());
120 u_row_ptrs.push(u_values.len());
121 }
122
123 Self {
124 l_values,
125 l_col_indices,
126 l_row_ptrs,
127 u_values,
128 u_col_indices,
129 u_row_ptrs,
130 u_diag,
131 n,
132 }
133 }
134}
135
136impl<T: ComplexField> Preconditioner<T> for IluPreconditioner<T> {
137 fn apply(&self, r: &Array1<T>) -> Array1<T> {
138 let mut y = r.clone();
139
140 for i in 0..self.n {
142 for idx in self.l_row_ptrs[i]..self.l_row_ptrs[i + 1] {
143 let j = self.l_col_indices[idx];
144 let l_ij = self.l_values[idx];
145 y[i] = y[i] - l_ij * y[j];
146 }
147 }
148
149 let mut x = y;
151 for i in (0..self.n).rev() {
152 for idx in self.u_row_ptrs[i]..self.u_row_ptrs[i + 1] {
153 let j = self.u_col_indices[idx];
154 if j > i {
155 let u_ij = self.u_values[idx];
156 x[i] = x[i] - u_ij * x[j];
157 }
158 }
159
160 let u_ii = self.u_diag[i];
161 if u_ii.norm() > T::Real::from_f64(1e-30).unwrap() {
162 x[i] *= u_ii.inv();
163 }
164 }
165
166 x
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::iterative::{GmresConfig, gmres, gmres_preconditioned};
174 use ndarray::array;
175 use num_complex::Complex64;
176
177 #[test]
178 fn test_ilu_preconditioner() {
179 let dense = array![
181 [
182 Complex64::new(4.0, 0.0),
183 Complex64::new(-1.0, 0.0),
184 Complex64::new(0.0, 0.0)
185 ],
186 [
187 Complex64::new(-1.0, 0.0),
188 Complex64::new(4.0, 0.0),
189 Complex64::new(-1.0, 0.0)
190 ],
191 [
192 Complex64::new(0.0, 0.0),
193 Complex64::new(-1.0, 0.0),
194 Complex64::new(4.0, 0.0)
195 ],
196 ];
197
198 let matrix = CsrMatrix::from_dense(&dense, 1e-15);
199 let precond = IluPreconditioner::from_csr(&matrix);
200
201 let r = array![
202 Complex64::new(1.0, 0.0),
203 Complex64::new(2.0, 0.0),
204 Complex64::new(3.0, 0.0)
205 ];
206
207 let result = precond.apply(&r);
208
209 let check = matrix.matvec(&result);
212 for i in 0..3 {
213 assert!(
215 (check[i] - r[i]).norm() < 0.5,
216 "ILU should approximately invert: got {:?} expected {:?}",
217 check[i],
218 r[i]
219 );
220 }
221 }
222
223 #[test]
224 fn test_ilu_with_gmres() {
225 let n = 10;
227 let mut dense = ndarray::Array2::from_elem((n, n), Complex64::new(0.0, 0.0));
228
229 for i in 0..n {
231 dense[[i, i]] = Complex64::new(4.0, 0.0);
232 if i > 0 {
233 dense[[i, i - 1]] = Complex64::new(-1.0, 0.0);
234 }
235 if i < n - 1 {
236 dense[[i, i + 1]] = Complex64::new(-1.0, 0.0);
237 }
238 }
239
240 let matrix = CsrMatrix::from_dense(&dense, 1e-15);
241 let precond = IluPreconditioner::from_csr(&matrix);
242
243 let b = Array1::from_iter((0..n).map(|i| Complex64::new((i as f64).sin(), 0.0)));
244
245 let config = GmresConfig {
246 max_iterations: 50,
247 restart: 10,
248 tolerance: 1e-10,
249 print_interval: 0,
250 };
251
252 let sol_no_precond = gmres(&matrix, &b, &config);
254
255 let sol_precond = gmres_preconditioned(&matrix, &precond, &b, &config);
257
258 assert!(sol_no_precond.converged);
260 assert!(sol_precond.converged);
261
262 assert!(
265 sol_precond.iterations <= sol_no_precond.iterations + 5,
266 "Preconditioning should not hurt: {} vs {}",
267 sol_precond.iterations,
268 sol_no_precond.iterations
269 );
270 }
271}