1use ndarray::{Array1, Array2};
24use num_complex::Complex64;
25
26use super::preconditioner::Preconditioner;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum IluMethod {
31 Tbem,
33 Slfmm,
35 Mlfmm,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum IluScanningDegree {
42 Coarse = 0,
44 Medium = 1,
46 Fine = 2,
48 Finest = 3,
50}
51
52fn get_threshold_factor(method: IluMethod, degree: IluScanningDegree) -> f64 {
54 match method {
55 IluMethod::Tbem => match degree {
56 IluScanningDegree::Coarse => 1.2,
57 IluScanningDegree::Medium => 1.0,
58 IluScanningDegree::Fine => 0.8,
59 IluScanningDegree::Finest => 0.6,
60 },
61 IluMethod::Slfmm => match degree {
62 IluScanningDegree::Coarse => 0.9,
63 IluScanningDegree::Medium => 0.35,
64 IluScanningDegree::Fine => 0.07,
65 IluScanningDegree::Finest => 0.01,
66 },
67 IluMethod::Mlfmm => match degree {
68 IluScanningDegree::Coarse => 0.65,
69 IluScanningDegree::Medium => 0.15,
70 IluScanningDegree::Fine => 0.05,
71 IluScanningDegree::Finest => 0.005,
72 },
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct IluSetup {
79 pub preconditioner: IluPreconditioner,
81 pub scaled_matrix: Array2<Complex64>,
83 pub row_scale: Array1<Complex64>,
85}
86
87#[derive(Debug, Clone)]
92pub struct IluPreconditioner {
93 l_values: Vec<Complex64>,
95 l_col_indices: Vec<usize>,
97 l_row_ptr: Vec<usize>,
99
100 u_values: Vec<Complex64>,
102 u_row_indices: Vec<usize>,
104 u_col_ptr: Vec<usize>,
106
107 n: usize,
109}
110
111impl IluPreconditioner {
112 pub fn from_matrix(
119 a: &Array2<Complex64>,
120 method: IluMethod,
121 degree: IluScanningDegree,
122 ) -> Self {
123 let n = a.nrows();
124 assert_eq!(n, a.ncols(), "Matrix must be square");
125
126 let threshold = get_threshold_factor(method, degree);
127
128 let mut scaled = a.clone();
130 let mut rhs_scale = vec![Complex64::new(1.0, 0.0); n];
131
132 for i in 0..n {
135 let row_sum_sq: f64 = scaled.row(i).iter().map(|x| x.norm_sqr()).sum();
136 if row_sum_sq > 1e-30 {
137 let scale = (n as f64 / row_sum_sq).sqrt();
138 for j in 0..n {
139 scaled[[i, j]] *= scale;
140 }
141 rhs_scale[i] = Complex64::new(scale, 0.0);
142 }
143 }
144
145 let mut keep = vec![vec![false; n]; n];
148 let mut n_trues = 0;
149
150 for i in 0..n {
151 for j in 0..n {
152 if scaled[[i, j]].norm() > threshold || i == j {
153 keep[i][j] = true;
154 n_trues += 1;
155 }
156 }
157 }
158
159 let mut jcol_tru = vec![0usize; n_trues];
163 let mut nrow_tru = vec![0usize; n + 1];
164
165 let mut kl = 0;
166 for i in 0..n {
167 nrow_tru[i] = kl;
168 for j in 0..n {
169 if keep[i][j] {
170 jcol_tru[kl] = j;
171 kl += 1;
172 }
173 }
174 }
175 nrow_tru[n] = kl;
176
177 let mut irow_tru = vec![0usize; n_trues];
181 let mut ncol_tru = vec![0usize; n + 1];
182
183 kl = 0;
184 for j in 0..n {
185 ncol_tru[j] = kl;
186 for i in 0..n {
187 for k in nrow_tru[i]..nrow_tru[i + 1] {
189 if jcol_tru[k] == j {
190 irow_tru[kl] = i;
191 kl += 1;
192 break;
193 }
194 }
195 }
196 }
197 ncol_tru[n] = kl;
198
199 let mut nunz_l = 0;
203 let mut nunz_u = 0;
204
205 for i in 0..n {
206 for k in nrow_tru[i]..nrow_tru[i + 1] {
208 if jcol_tru[k] <= i {
209 nunz_l += 1;
210 }
211 }
212 for k in ncol_tru[i]..ncol_tru[i + 1] {
214 if irow_tru[k] < i {
215 nunz_u += 1;
216 }
217 }
218 }
219
220 let mut l_col_indices = vec![0usize; nunz_l];
222 let mut l_row_ptr = vec![0usize; n + 1];
223 let mut u_row_indices = vec![0usize; nunz_u];
224 let mut u_col_ptr = vec![0usize; n + 1];
225 let mut l_values = vec![Complex64::new(0.0, 0.0); nunz_l];
226 let mut u_values = vec![Complex64::new(0.0, 0.0); nunz_u];
227
228 kl = 0;
230 let mut ku = 0;
231 for i in 0..n {
232 l_row_ptr[i] = kl;
233 for k in nrow_tru[i]..nrow_tru[i + 1] {
234 let j = jcol_tru[k];
235 if j <= i {
236 l_col_indices[kl] = j;
237 l_values[kl] = scaled[[i, j]];
238 kl += 1;
239 }
240 }
241
242 u_col_ptr[i] = ku;
243 for k in ncol_tru[i]..ncol_tru[i + 1] {
244 let row = irow_tru[k];
245 if row < i {
246 u_row_indices[ku] = row;
247 u_values[ku] = scaled[[row, i]];
248 ku += 1;
249 }
250 }
251 }
252 l_row_ptr[n] = kl;
253 u_col_ptr[n] = ku;
254
255 Self::compute_ilu_factorization(
258 n,
259 &mut l_values,
260 &l_col_indices,
261 &l_row_ptr,
262 &mut u_values,
263 &u_row_indices,
264 &u_col_ptr,
265 &jcol_tru,
266 &nrow_tru,
267 &irow_tru,
268 &ncol_tru,
269 );
270
271 IluPreconditioner {
272 l_values,
273 l_col_indices,
274 l_row_ptr,
275 u_values,
276 u_row_indices,
277 u_col_ptr,
278 n,
279 }
280 }
281
282 pub fn setup_system(
297 a: &Array2<Complex64>,
298 method: IluMethod,
299 degree: IluScanningDegree,
300 ) -> IluSetup {
301 let threshold = get_threshold_factor(method, degree);
302 Self::setup_system_with_threshold(a, threshold)
303 }
304
305 pub fn setup_system_with_threshold(a: &Array2<Complex64>, threshold: f64) -> IluSetup {
310 let n = a.nrows();
311 assert_eq!(n, a.ncols(), "Matrix must be square");
312
313 let mut scaled = a.clone();
315 let mut row_scale = Array1::from_elem(n, Complex64::new(1.0, 0.0));
316
317 for i in 0..n {
319 let row_sum_sq: f64 = scaled.row(i).iter().map(|x| x.norm_sqr()).sum();
320 if row_sum_sq > 1e-30 {
321 let scale = (n as f64 / row_sum_sq).sqrt();
322 for j in 0..n {
323 scaled[[i, j]] *= scale;
324 }
325 row_scale[i] = Complex64::new(scale, 0.0);
326 }
327 }
328
329 let mut keep = vec![vec![false; n]; n];
331 let mut n_trues = 0;
332
333 for i in 0..n {
334 for j in 0..n {
335 if scaled[[i, j]].norm() > threshold || i == j {
336 keep[i][j] = true;
337 n_trues += 1;
338 }
339 }
340 }
341
342 let mut jcol_tru = vec![0usize; n_trues];
343 let mut nrow_tru = vec![0usize; n + 1];
344
345 let mut kl = 0;
346 for i in 0..n {
347 nrow_tru[i] = kl;
348 for j in 0..n {
349 if keep[i][j] {
350 jcol_tru[kl] = j;
351 kl += 1;
352 }
353 }
354 }
355 nrow_tru[n] = kl;
356
357 let mut irow_tru = vec![0usize; n_trues];
358 let mut ncol_tru = vec![0usize; n + 1];
359
360 kl = 0;
361 for j in 0..n {
362 ncol_tru[j] = kl;
363 for i in 0..n {
364 for k in nrow_tru[i]..nrow_tru[i + 1] {
365 if jcol_tru[k] == j {
366 irow_tru[kl] = i;
367 kl += 1;
368 break;
369 }
370 }
371 }
372 }
373 ncol_tru[n] = kl;
374
375 let mut nunz_l = 0;
376 let mut nunz_u = 0;
377
378 for i in 0..n {
379 for k in nrow_tru[i]..nrow_tru[i + 1] {
380 if jcol_tru[k] <= i {
381 nunz_l += 1;
382 }
383 }
384 for k in ncol_tru[i]..ncol_tru[i + 1] {
385 if irow_tru[k] < i {
386 nunz_u += 1;
387 }
388 }
389 }
390
391 let mut l_col_indices = vec![0usize; nunz_l];
392 let mut l_row_ptr = vec![0usize; n + 1];
393 let mut u_row_indices = vec![0usize; nunz_u];
394 let mut u_col_ptr = vec![0usize; n + 1];
395 let mut l_values = vec![Complex64::new(0.0, 0.0); nunz_l];
396 let mut u_values = vec![Complex64::new(0.0, 0.0); nunz_u];
397
398 kl = 0;
399 let mut ku = 0;
400 for i in 0..n {
401 l_row_ptr[i] = kl;
402 for k in nrow_tru[i]..nrow_tru[i + 1] {
403 let j = jcol_tru[k];
404 if j <= i {
405 l_col_indices[kl] = j;
406 l_values[kl] = scaled[[i, j]];
407 kl += 1;
408 }
409 }
410
411 u_col_ptr[i] = ku;
412 for k in ncol_tru[i]..ncol_tru[i + 1] {
413 let row = irow_tru[k];
414 if row < i {
415 u_row_indices[ku] = row;
416 u_values[ku] = scaled[[row, i]];
417 ku += 1;
418 }
419 }
420 }
421 l_row_ptr[n] = kl;
422 u_col_ptr[n] = ku;
423
424 Self::compute_ilu_factorization(
425 n,
426 &mut l_values,
427 &l_col_indices,
428 &l_row_ptr,
429 &mut u_values,
430 &u_row_indices,
431 &u_col_ptr,
432 &jcol_tru,
433 &nrow_tru,
434 &irow_tru,
435 &ncol_tru,
436 );
437
438 let preconditioner = IluPreconditioner {
439 l_values,
440 l_col_indices,
441 l_row_ptr,
442 u_values,
443 u_row_indices,
444 u_col_ptr,
445 n,
446 };
447
448 IluSetup {
449 preconditioner,
450 scaled_matrix: scaled,
451 row_scale,
452 }
453 }
454
455 pub fn from_matrix_with_threshold(a: &Array2<Complex64>, threshold: f64) -> Self {
457 let n = a.nrows();
458 assert_eq!(n, a.ncols(), "Matrix must be square");
459
460 let mut scaled = a.clone();
462
463 for i in 0..n {
465 let row_sum_sq: f64 = scaled.row(i).iter().map(|x| x.norm_sqr()).sum();
466 if row_sum_sq > 1e-30 {
467 let scale = (n as f64 / row_sum_sq).sqrt();
468 for j in 0..n {
469 scaled[[i, j]] *= scale;
470 }
471 }
472 }
473
474 let mut keep = vec![vec![false; n]; n];
476 let mut n_trues = 0;
477
478 for i in 0..n {
479 for j in 0..n {
480 if scaled[[i, j]].norm() > threshold || i == j {
481 keep[i][j] = true;
482 n_trues += 1;
483 }
484 }
485 }
486
487 let mut jcol_tru = vec![0usize; n_trues];
489 let mut nrow_tru = vec![0usize; n + 1];
490
491 let mut kl = 0;
492 for i in 0..n {
493 nrow_tru[i] = kl;
494 for j in 0..n {
495 if keep[i][j] {
496 jcol_tru[kl] = j;
497 kl += 1;
498 }
499 }
500 }
501 nrow_tru[n] = kl;
502
503 let mut irow_tru = vec![0usize; n_trues];
504 let mut ncol_tru = vec![0usize; n + 1];
505
506 kl = 0;
507 for j in 0..n {
508 ncol_tru[j] = kl;
509 for i in 0..n {
510 for k in nrow_tru[i]..nrow_tru[i + 1] {
511 if jcol_tru[k] == j {
512 irow_tru[kl] = i;
513 kl += 1;
514 break;
515 }
516 }
517 }
518 }
519 ncol_tru[n] = kl;
520
521 let mut nunz_l = 0;
522 let mut nunz_u = 0;
523
524 for i in 0..n {
525 for k in nrow_tru[i]..nrow_tru[i + 1] {
526 if jcol_tru[k] <= i {
527 nunz_l += 1;
528 }
529 }
530 for k in ncol_tru[i]..ncol_tru[i + 1] {
531 if irow_tru[k] < i {
532 nunz_u += 1;
533 }
534 }
535 }
536
537 let mut l_col_indices = vec![0usize; nunz_l];
538 let mut l_row_ptr = vec![0usize; n + 1];
539 let mut u_row_indices = vec![0usize; nunz_u];
540 let mut u_col_ptr = vec![0usize; n + 1];
541 let mut l_values = vec![Complex64::new(0.0, 0.0); nunz_l];
542 let mut u_values = vec![Complex64::new(0.0, 0.0); nunz_u];
543
544 kl = 0;
545 let mut ku = 0;
546 for i in 0..n {
547 l_row_ptr[i] = kl;
548 for k in nrow_tru[i]..nrow_tru[i + 1] {
549 let j = jcol_tru[k];
550 if j <= i {
551 l_col_indices[kl] = j;
552 l_values[kl] = scaled[[i, j]];
553 kl += 1;
554 }
555 }
556
557 u_col_ptr[i] = ku;
558 for k in ncol_tru[i]..ncol_tru[i + 1] {
559 let row = irow_tru[k];
560 if row < i {
561 u_row_indices[ku] = row;
562 u_values[ku] = scaled[[row, i]];
563 ku += 1;
564 }
565 }
566 }
567 l_row_ptr[n] = kl;
568 u_col_ptr[n] = ku;
569
570 Self::compute_ilu_factorization(
571 n,
572 &mut l_values,
573 &l_col_indices,
574 &l_row_ptr,
575 &mut u_values,
576 &u_row_indices,
577 &u_col_ptr,
578 &jcol_tru,
579 &nrow_tru,
580 &irow_tru,
581 &ncol_tru,
582 );
583
584 IluPreconditioner {
585 l_values,
586 l_col_indices,
587 l_row_ptr,
588 u_values,
589 u_row_indices,
590 u_col_ptr,
591 n,
592 }
593 }
594
595 #[allow(clippy::too_many_arguments)]
600 fn compute_ilu_factorization(
601 n: usize,
602 l_values: &mut [Complex64],
603 l_col_indices: &[usize],
604 l_row_ptr: &[usize],
605 u_values: &mut [Complex64],
606 u_row_indices: &[usize],
607 u_col_ptr: &[usize],
608 jcol_tru: &[usize],
609 nrow_tru: &[usize],
610 irow_tru: &[usize],
611 ncol_tru: &[usize],
612 ) {
613 let mut mi_row = vec![false; n];
615 let mut mi_col = vec![false; n];
616 let mut mk_vct = vec![false; n];
617
618 for i in 0..n {
619 for j in 0..n {
621 mi_row[j] = false;
622 mi_col[j] = false;
623 }
624 for k in nrow_tru[i]..nrow_tru[i + 1] {
626 mi_row[jcol_tru[k]] = true;
627 }
628 for k in ncol_tru[i]..ncol_tru[i + 1] {
630 mi_col[irow_tru[k]] = true;
631 }
632
633 for k in i..n {
635 if !mi_col[k] {
636 continue;
637 }
638
639 let mut j1 = 0;
641 for j in l_row_ptr[k]..l_row_ptr[k + 1] {
642 if l_col_indices[j] == i {
643 j1 = j;
644 break;
645 }
646 }
647
648 for j in 0..n {
650 mk_vct[j] = false;
651 }
652 for j in nrow_tru[k]..nrow_tru[k + 1] {
653 mk_vct[jcol_tru[j]] = true;
654 }
655
656 let mut ml = 0;
658 let mut mu = 0;
659 for m in 0..i {
660 if mk_vct[m] && mi_col[m] {
661 l_values[j1] -=
662 l_values[l_row_ptr[k] + ml] * u_values[u_col_ptr[i] + mu];
663 }
664 if mk_vct[m] {
665 ml += 1;
666 }
667 if mi_col[m] {
668 mu += 1;
669 }
670 }
671 }
672
673 for k in (i + 1)..n {
675 if !mi_row[k] {
676 continue;
677 }
678
679 let mut j1 = 0;
681 for j in u_col_ptr[k]..u_col_ptr[k + 1] {
682 if u_row_indices[j] == i {
683 j1 = j;
684 break;
685 }
686 }
687
688 for j in 0..n {
690 mk_vct[j] = false;
691 }
692 for j in ncol_tru[k]..ncol_tru[k + 1] {
693 mk_vct[irow_tru[j]] = true;
694 }
695
696 let mut ml = 0;
698 let mut mu = 0;
699 for m in 0..i {
700 if mi_row[m] && mk_vct[m] {
701 u_values[j1] -=
702 l_values[l_row_ptr[i] + ml] * u_values[u_col_ptr[k] + mu];
703 }
704 if mi_row[m] {
705 ml += 1;
706 }
707 if mk_vct[m] {
708 mu += 1;
709 }
710 }
711
712 let l_diag_idx = l_row_ptr[i + 1] - 1;
714 if l_values[l_diag_idx].norm() > 1e-30 {
715 u_values[j1] /= l_values[l_diag_idx];
716 }
717 }
718 }
719 }
720
721 fn forward_backward(&self, r: &Array1<Complex64>) -> Array1<Complex64> {
726 let mut z = r.clone();
727
728 if self.l_values.is_empty() {
731 return z;
732 }
733
734 let l_diag_0 = self.l_values[0];
736 if l_diag_0.norm() > 1e-30 {
737 z[0] /= l_diag_0;
738 }
739
740 for i in 1..self.n {
741 let row_end = self.l_row_ptr[i + 1];
744 let diag_idx = row_end - 1;
745
746 for k in self.l_row_ptr[i]..diag_idx {
747 let j = self.l_col_indices[k];
748 let z_j = z[j];
749 z[i] -= self.l_values[k] * z_j;
750 }
751
752 if self.l_values[diag_idx].norm() > 1e-30 {
754 z[i] /= self.l_values[diag_idx];
755 }
756 }
757
758 for i in (1..self.n).rev() {
762 let z_i = z[i];
765 for k in self.u_col_ptr[i]..self.u_col_ptr[i + 1] {
766 let row = self.u_row_indices[k];
767 z[row] -= self.u_values[k] * z_i;
768 }
769 }
770
771 z
772 }
773
774 pub fn nnz_l(&self) -> usize {
776 self.l_values.len()
777 }
778
779 pub fn nnz_u(&self) -> usize {
781 self.u_values.len()
782 }
783
784 pub fn fill_ratio(&self) -> f64 {
786 (self.l_values.len() + self.u_values.len()) as f64 / (self.n * self.n) as f64
787 }
788}
789
790impl Preconditioner for IluPreconditioner {
791 fn apply(&self, r: &Array1<Complex64>) -> Array1<Complex64> {
792 self.forward_backward(r)
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use super::*;
799
800 #[test]
801 fn test_ilu_simple() {
802 let a = Array2::from_shape_vec(
804 (3, 3),
805 vec![
806 Complex64::new(4.0, 0.0),
807 Complex64::new(1.0, 0.0),
808 Complex64::new(0.0, 0.0),
809 Complex64::new(1.0, 0.0),
810 Complex64::new(5.0, 0.0),
811 Complex64::new(2.0, 0.0),
812 Complex64::new(0.0, 0.0),
813 Complex64::new(2.0, 0.0),
814 Complex64::new(6.0, 0.0),
815 ],
816 )
817 .unwrap();
818
819 let precond = IluPreconditioner::from_matrix(&a, IluMethod::Tbem, IluScanningDegree::Coarse);
820
821 assert!(precond.nnz_l() > 0);
823
824 let r = Array1::from_vec(vec![
826 Complex64::new(1.0, 0.0),
827 Complex64::new(1.0, 0.0),
828 Complex64::new(1.0, 0.0),
829 ]);
830 let z = precond.apply(&r);
831
832 assert_eq!(z.len(), 3);
834 assert!(z.iter().all(|x| x.is_finite()));
835 }
836
837 #[test]
838 fn test_ilu_complex() {
839 let a = Array2::from_shape_vec(
841 (3, 3),
842 vec![
843 Complex64::new(4.0, 1.0),
844 Complex64::new(1.0, -0.5),
845 Complex64::new(0.5, 0.0),
846 Complex64::new(1.0, 0.5),
847 Complex64::new(5.0, -1.0),
848 Complex64::new(2.0, 0.3),
849 Complex64::new(0.5, 0.0),
850 Complex64::new(2.0, -0.3),
851 Complex64::new(6.0, 2.0),
852 ],
853 )
854 .unwrap();
855
856 let precond = IluPreconditioner::from_matrix(&a, IluMethod::Tbem, IluScanningDegree::Fine);
857
858 let r = Array1::from_vec(vec![
859 Complex64::new(1.0, 0.5),
860 Complex64::new(2.0, -0.5),
861 Complex64::new(0.5, 1.0),
862 ]);
863 let z = precond.apply(&r);
864
865 assert_eq!(z.len(), 3);
866 assert!(z.iter().all(|x| x.is_finite()));
867 }
868
869 #[test]
870 fn test_ilu_fill_ratio() {
871 let n = 10;
873 let mut a = Array2::zeros((n, n));
874 for i in 0..n {
875 for j in 0..n {
876 a[[i, j]] = Complex64::new((i + j) as f64 + 1.0, 0.0);
877 }
878 a[[i, i]] = Complex64::new((n * 2) as f64, 0.0);
880 }
881
882 let precond_coarse =
883 IluPreconditioner::from_matrix(&a, IluMethod::Tbem, IluScanningDegree::Coarse);
884 let precond_finest =
885 IluPreconditioner::from_matrix(&a, IluMethod::Tbem, IluScanningDegree::Finest);
886
887 assert!(precond_finest.fill_ratio() >= precond_coarse.fill_ratio());
889 }
890}