1use ndarray::{Array1, Array2};
28use num_complex::Complex64;
29
30use crate::core::assembly::mlfmm::MlfmmSystem;
31use crate::core::assembly::slfmm::SlfmmSystem;
32use crate::core::assembly::sparse::CsrMatrix;
33
34pub use super::ilu_preconditioner::{IluMethod, IluPreconditioner, IluScanningDegree, IluSetup};
35pub use super::preconditioner::Preconditioner;
36
37pub trait LinearOperator: Send + Sync {
45 fn num_rows(&self) -> usize;
47
48 fn num_cols(&self) -> usize;
50
51 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64>;
53
54 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64>;
56
57 fn is_square(&self) -> bool {
59 self.num_rows() == self.num_cols()
60 }
61}
62
63pub struct DenseOperator {
65 matrix: ndarray::Array2<Complex64>,
66}
67
68impl DenseOperator {
69 pub fn new(matrix: ndarray::Array2<Complex64>) -> Self {
71 Self { matrix }
72 }
73}
74
75impl LinearOperator for DenseOperator {
76 fn num_rows(&self) -> usize {
77 self.matrix.nrows()
78 }
79
80 fn num_cols(&self) -> usize {
81 self.matrix.ncols()
82 }
83
84 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
85 self.matrix.dot(x)
86 }
87
88 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
89 self.matrix.t().dot(x)
90 }
91}
92
93pub struct SlfmmOperator {
95 system: SlfmmSystem,
96}
97
98impl SlfmmOperator {
99 pub fn new(system: SlfmmSystem) -> Self {
101 Self { system }
102 }
103
104 pub fn system(&self) -> &SlfmmSystem {
106 &self.system
107 }
108
109 pub fn rhs(&self) -> &Array1<Complex64> {
111 &self.system.rhs
112 }
113}
114
115impl LinearOperator for SlfmmOperator {
116 fn num_rows(&self) -> usize {
117 self.system.num_dofs
118 }
119
120 fn num_cols(&self) -> usize {
121 self.system.num_dofs
122 }
123
124 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
125 self.system.matvec(x)
126 }
127
128 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
129 self.system.matvec_transpose(x)
130 }
131}
132
133pub struct MlfmmOperator {
135 system: MlfmmSystem,
136}
137
138impl MlfmmOperator {
139 pub fn new(system: MlfmmSystem) -> Self {
141 Self { system }
142 }
143
144 pub fn system(&self) -> &MlfmmSystem {
146 &self.system
147 }
148
149 pub fn rhs(&self) -> &Array1<Complex64> {
151 &self.system.rhs
152 }
153}
154
155impl LinearOperator for MlfmmOperator {
156 fn num_rows(&self) -> usize {
157 self.system.num_dofs
158 }
159
160 fn num_cols(&self) -> usize {
161 self.system.num_dofs
162 }
163
164 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
165 self.system.matvec(x)
166 }
167
168 fn apply_transpose(&self, _x: &Array1<Complex64>) -> Array1<Complex64> {
169 unimplemented!("MLFMM transpose not yet implemented")
171 }
172}
173
174pub struct CsrOperator {
176 matrix: CsrMatrix,
177}
178
179impl CsrOperator {
180 pub fn new(matrix: CsrMatrix) -> Self {
182 Self { matrix }
183 }
184
185 pub fn matrix(&self) -> &CsrMatrix {
187 &self.matrix
188 }
189}
190
191impl LinearOperator for CsrOperator {
192 fn num_rows(&self) -> usize {
193 self.matrix.num_rows
194 }
195
196 fn num_cols(&self) -> usize {
197 self.matrix.num_cols
198 }
199
200 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
201 self.matrix.matvec(x)
202 }
203
204 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
205 self.matrix.matvec_transpose(x)
206 }
207}
208
209pub fn solve_cgs<O: LinearOperator>(
219 operator: &O,
220 b: &Array1<Complex64>,
221 config: &super::cgs::CgsConfig,
222) -> super::cgs::CgsSolution {
223 let matvec = |x: &Array1<Complex64>| operator.apply(x);
224 super::cgs::cgs_solve(matvec, b, None, config)
225}
226
227pub fn solve_bicgstab<O: LinearOperator>(
237 operator: &O,
238 b: &Array1<Complex64>,
239 config: &super::bicgstab::BiCgstabConfig,
240) -> super::bicgstab::BiCgstabSolution {
241 let matvec = |x: &Array1<Complex64>| operator.apply(x);
242 super::bicgstab::bicgstab_solve(matvec, b, None, config)
243}
244
245pub fn solve_adaptive<O: LinearOperator>(
258 operator: &O,
259 b: &Array1<Complex64>,
260 use_iterative: bool,
261) -> Array1<Complex64> {
262 let n = operator.num_rows();
263
264 if !use_iterative && n < 1000 {
265 eprintln!(
268 "Warning: solve_adaptive called with operator on small system (n={}). \
269 Consider using direct solver.",
270 n
271 );
272 }
273
274 let config = super::cgs::CgsConfig {
276 max_iterations: n.max(100),
277 tolerance: 1e-8,
278 print_interval: 0,
279 };
280
281 let solution = solve_cgs(operator, b, &config);
282
283 if !solution.converged {
284 eprintln!(
285 "Warning: CGS did not converge after {} iterations (residual = {:.2e})",
286 solution.iterations, solution.residual
287 );
288 }
289
290 solution.x
291}
292
293pub struct DiagonalPreconditioner {
297 inv_diag: Array1<Complex64>,
298}
299
300impl DiagonalPreconditioner {
301 pub fn from_csr(matrix: &CsrMatrix) -> Self {
303 let diag = matrix.diagonal();
304 let inv_diag = diag.mapv(|d| {
305 if d.norm() > 1e-15 {
306 Complex64::new(1.0, 0.0) / d
307 } else {
308 Complex64::new(1.0, 0.0)
309 }
310 });
311 Self { inv_diag }
312 }
313
314 pub fn from_diagonal(diag: Array1<Complex64>) -> Self {
316 let inv_diag = diag.mapv(|d| {
317 if d.norm() > 1e-15 {
318 Complex64::new(1.0, 0.0) / d
319 } else {
320 Complex64::new(1.0, 0.0)
321 }
322 });
323 Self { inv_diag }
324 }
325
326 pub fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
328 x * &self.inv_diag
329 }
330}
331
332impl LinearOperator for DiagonalPreconditioner {
333 fn num_rows(&self) -> usize {
334 self.inv_diag.len()
335 }
336
337 fn num_cols(&self) -> usize {
338 self.inv_diag.len()
339 }
340
341 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
342 x * &self.inv_diag
343 }
344
345 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
346 x * &self.inv_diag
348 }
349}
350
351pub struct IluOperator {
355 preconditioner: IluPreconditioner,
356 n: usize,
357}
358
359impl IluOperator {
360 pub fn new(preconditioner: IluPreconditioner, n: usize) -> Self {
362 Self { preconditioner, n }
363 }
364
365 pub fn from_tbem_matrix(matrix: &Array2<Complex64>) -> Self {
367 let n = matrix.nrows();
368 let precond =
369 IluPreconditioner::from_matrix(matrix, IluMethod::Tbem, IluScanningDegree::Fine);
370 Self {
371 preconditioner: precond,
372 n,
373 }
374 }
375
376 pub fn from_matrix(
378 matrix: &Array2<Complex64>,
379 method: IluMethod,
380 degree: IluScanningDegree,
381 ) -> Self {
382 let n = matrix.nrows();
383 let precond = IluPreconditioner::from_matrix(matrix, method, degree);
384 Self {
385 preconditioner: precond,
386 n,
387 }
388 }
389
390 pub fn fill_ratio(&self) -> f64 {
392 self.preconditioner.fill_ratio()
393 }
394}
395
396impl LinearOperator for IluOperator {
397 fn num_rows(&self) -> usize {
398 self.n
399 }
400
401 fn num_cols(&self) -> usize {
402 self.n
403 }
404
405 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
406 self.preconditioner.apply(x)
407 }
408
409 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
410 self.preconditioner.apply(x)
413 }
414}
415
416pub fn solve_with_ilu(
443 matrix: &Array2<Complex64>,
444 b: &Array1<Complex64>,
445 method: IluMethod,
446 degree: IluScanningDegree,
447 config: &super::cgs::CgsConfig,
448) -> super::cgs::CgsSolution {
449 let setup = IluPreconditioner::setup_system(matrix, method, degree);
451
452 let scaled_b: Array1<Complex64> = b
454 .iter()
455 .zip(setup.row_scale.iter())
456 .map(|(&bi, &si)| bi * si)
457 .collect();
458
459 let matvec = |x: &Array1<Complex64>| setup.scaled_matrix.dot(x);
461
462 let precond_solve = |r: &Array1<Complex64>| setup.preconditioner.apply(r);
464
465 super::cgs::cgs_solve_preconditioned(matvec, precond_solve, &scaled_b, None, config)
467}
468
469pub fn solve_with_ilu_operator<O: LinearOperator>(
482 operator: &O,
483 nearfield_matrix: &Array2<Complex64>,
484 b: &Array1<Complex64>,
485 method: IluMethod,
486 degree: IluScanningDegree,
487 config: &super::cgs::CgsConfig,
488) -> super::cgs::CgsSolution {
489 let setup = IluPreconditioner::setup_system(nearfield_matrix, method, degree);
491
492 let scaled_b: Array1<Complex64> = b
494 .iter()
495 .zip(setup.row_scale.iter())
496 .map(|(&bi, &si)| bi * si)
497 .collect();
498
499 let matvec = |x: &Array1<Complex64>| {
501 let y = operator.apply(x);
502 y.iter()
504 .zip(setup.row_scale.iter())
505 .map(|(&yi, &si)| yi * si)
506 .collect()
507 };
508
509 let precond_solve = |r: &Array1<Complex64>| setup.preconditioner.apply(r);
511
512 super::cgs::cgs_solve_preconditioned(matvec, precond_solve, &scaled_b, None, config)
513}
514
515pub fn solve_tbem_with_ilu(
519 matrix: &Array2<Complex64>,
520 b: &Array1<Complex64>,
521 config: &super::cgs::CgsConfig,
522) -> super::cgs::CgsSolution {
523 solve_with_ilu(matrix, b, IluMethod::Tbem, IluScanningDegree::Fine, config)
524}
525
526#[derive(Debug, Clone)]
528pub struct IluDiagnostics {
529 pub nnz_l: usize,
531 pub nnz_u: usize,
533 pub fill_ratio: f64,
535 pub threshold_used: f64,
537}
538
539pub fn ilu_diagnostics(
541 matrix: &Array2<Complex64>,
542 method: IluMethod,
543 degree: IluScanningDegree,
544) -> IluDiagnostics {
545 let precond = IluPreconditioner::from_matrix(matrix, method, degree);
546
547 let threshold = match method {
549 IluMethod::Tbem => match degree {
550 IluScanningDegree::Coarse => 1.2,
551 IluScanningDegree::Medium => 1.0,
552 IluScanningDegree::Fine => 0.8,
553 IluScanningDegree::Finest => 0.6,
554 },
555 IluMethod::Slfmm => match degree {
556 IluScanningDegree::Coarse => 0.9,
557 IluScanningDegree::Medium => 0.35,
558 IluScanningDegree::Fine => 0.07,
559 IluScanningDegree::Finest => 0.01,
560 },
561 IluMethod::Mlfmm => match degree {
562 IluScanningDegree::Coarse => 0.65,
563 IluScanningDegree::Medium => 0.15,
564 IluScanningDegree::Fine => 0.05,
565 IluScanningDegree::Finest => 0.005,
566 },
567 };
568
569 IluDiagnostics {
570 nnz_l: precond.nnz_l(),
571 nnz_u: precond.nnz_u(),
572 fill_ratio: precond.fill_ratio(),
573 threshold_used: threshold,
574 }
575}
576
577pub fn solve_gmres<O: LinearOperator>(
591 operator: &O,
592 b: &Array1<Complex64>,
593 config: &super::gmres::GmresConfig,
594) -> super::gmres::GmresSolution {
595 let matvec = |x: &Array1<Complex64>| operator.apply(x);
596 super::gmres::gmres_solve(matvec, b, None, config)
597}
598
599pub fn gmres_solve_with_ilu(
627 matrix: &Array2<Complex64>,
628 b: &Array1<Complex64>,
629 method: IluMethod,
630 degree: IluScanningDegree,
631 config: &super::gmres::GmresConfig,
632) -> super::gmres::GmresSolution {
633 let setup = IluPreconditioner::setup_system(matrix, method, degree);
635
636 let scaled_b: Array1<Complex64> = b
638 .iter()
639 .zip(setup.row_scale.iter())
640 .map(|(&bi, &si)| bi * si)
641 .collect();
642
643 let matvec = |x: &Array1<Complex64>| setup.scaled_matrix.dot(x);
645
646 let precond_solve = |r: &Array1<Complex64>| setup.preconditioner.apply(r);
648
649 super::gmres::gmres_solve_preconditioned(matvec, precond_solve, &scaled_b, None, config)
651}
652
653pub fn gmres_solve_tbem_with_ilu(
657 matrix: &Array2<Complex64>,
658 b: &Array1<Complex64>,
659 config: &super::gmres::GmresConfig,
660) -> super::gmres::GmresSolution {
661 gmres_solve_with_ilu(matrix, b, IluMethod::Tbem, IluScanningDegree::Fine, config)
662}
663
664pub fn gmres_solve_with_ilu_operator<O: LinearOperator>(
677 operator: &O,
678 nearfield_matrix: &Array2<Complex64>,
679 b: &Array1<Complex64>,
680 method: IluMethod,
681 degree: IluScanningDegree,
682 config: &super::gmres::GmresConfig,
683) -> super::gmres::GmresSolution {
684 let setup = IluPreconditioner::setup_system(nearfield_matrix, method, degree);
686
687 let scaled_b: Array1<Complex64> = b
689 .iter()
690 .zip(setup.row_scale.iter())
691 .map(|(&bi, &si)| bi * si)
692 .collect();
693
694 let matvec = |x: &Array1<Complex64>| {
696 let y = operator.apply(x);
697 y.iter()
699 .zip(setup.row_scale.iter())
700 .map(|(&yi, &si)| yi * si)
701 .collect()
702 };
703
704 let precond_solve = |r: &Array1<Complex64>| setup.preconditioner.apply(r);
706
707 super::gmres::gmres_solve_preconditioned(matvec, precond_solve, &scaled_b, None, config)
708}
709
710pub fn gmres_solve_with_hierarchical_precond(
729 fmm_system: &crate::core::assembly::slfmm::SlfmmSystem,
730 b: &Array1<Complex64>,
731 config: &super::gmres::GmresConfig,
732) -> super::gmres::GmresSolution {
733 use super::preconditioner::HierarchicalFmmPreconditioner;
734
735 let precond = HierarchicalFmmPreconditioner::from_slfmm(fmm_system);
737
738 let matvec = |x: &Array1<Complex64>| fmm_system.matvec(x);
740
741 let precond_solve = |r: &Array1<Complex64>| precond.apply(r);
743
744 super::gmres::gmres_solve_preconditioned(matvec, precond_solve, b, None, config)
746}
747
748pub fn gmres_solve_fmm_hierarchical(
752 fmm_operator: &SlfmmOperator,
753 config: &super::gmres::GmresConfig,
754) -> super::gmres::GmresSolution {
755 use super::preconditioner::HierarchicalFmmPreconditioner;
756
757 let precond = HierarchicalFmmPreconditioner::from_slfmm(fmm_operator.system());
759
760 let b = fmm_operator.rhs();
762
763 let matvec = |x: &Array1<Complex64>| fmm_operator.apply(x);
765 let precond_solve = |r: &Array1<Complex64>| precond.apply(r);
766
767 super::gmres::gmres_solve_preconditioned(matvec, precond_solve, b, None, config)
768}
769
770pub fn gmres_solve_fmm_batched(
789 fmm_system: &crate::core::assembly::slfmm::SlfmmSystem,
790 b: &Array1<Complex64>,
791 config: &super::gmres::GmresConfig,
792) -> super::gmres::GmresSolution {
793 use super::batched_blas::SlfmmMatvecWorkspace;
794 use std::cell::RefCell;
795
796 let workspace = RefCell::new(SlfmmMatvecWorkspace::new(
798 fmm_system.num_clusters,
799 fmm_system.num_sphere_points,
800 fmm_system.num_dofs,
801 ));
802
803 let matvec = |x: &Array1<Complex64>| {
806 super::batched_blas::slfmm_matvec_batched(fmm_system, x, &mut workspace.borrow_mut())
807 };
808
809 super::gmres::gmres_solve(matvec, b, None, config)
810}
811
812pub fn gmres_solve_fmm_batched_with_ilu(
816 fmm_system: &crate::core::assembly::slfmm::SlfmmSystem,
817 b: &Array1<Complex64>,
818 method: IluMethod,
819 degree: IluScanningDegree,
820 config: &super::gmres::GmresConfig,
821) -> super::gmres::GmresSolution {
822 use super::batched_blas::SlfmmMatvecWorkspace;
823 use std::cell::RefCell;
824
825 let nearfield_matrix = fmm_system.extract_near_field_matrix();
827
828 let setup = IluPreconditioner::setup_system(&nearfield_matrix, method, degree);
830
831 let scaled_b: Array1<Complex64> = b
833 .iter()
834 .zip(setup.row_scale.iter())
835 .map(|(&bi, &si)| bi * si)
836 .collect();
837
838 let workspace = RefCell::new(SlfmmMatvecWorkspace::new(
840 fmm_system.num_clusters,
841 fmm_system.num_sphere_points,
842 fmm_system.num_dofs,
843 ));
844
845 let matvec = |x: &Array1<Complex64>| {
847 let y = super::batched_blas::slfmm_matvec_batched(fmm_system, x, &mut workspace.borrow_mut());
848 y.iter()
850 .zip(setup.row_scale.iter())
851 .map(|(&yi, &si)| yi * si)
852 .collect()
853 };
854
855 let precond_solve = |r: &Array1<Complex64>| setup.preconditioner.apply(r);
857
858 super::gmres::gmres_solve_preconditioned(matvec, precond_solve, &scaled_b, None, config)
859}
860
861pub fn recommended_mesh_resolution(
878 frequency: f64,
879 speed_of_sound: f64,
880 elements_per_wavelength: usize,
881) -> f64 {
882 let wavelength = speed_of_sound / frequency;
883 elements_per_wavelength as f64 / wavelength
884}
885
886pub fn mesh_resolution_for_frequency_range(
890 min_freq: f64,
891 max_freq: f64,
892 speed_of_sound: f64,
893 elements_per_wavelength: usize,
894) -> f64 {
895 recommended_mesh_resolution(max_freq, speed_of_sound, elements_per_wavelength)
897}
898
899pub fn estimate_element_count(
903 room_dimensions: (f64, f64, f64), mesh_resolution: f64,
905) -> usize {
906 let (w, d, h) = room_dimensions;
907
908 let surface_area = 2.0 * (w * d + w * h + d * h);
910
911 let element_size = 1.0 / mesh_resolution;
913 let element_area = element_size * element_size;
914
915 (surface_area / element_area).ceil() as usize
916}
917
918pub struct AdaptiveMeshConfig {
920 pub base_resolution: f64,
922 pub source_refinement: f64,
924 pub source_refinement_radius: f64,
926}
927
928impl AdaptiveMeshConfig {
929 pub fn for_frequency_range(min_freq: f64, max_freq: f64) -> Self {
931 let speed_of_sound = 343.0;
932 let base = mesh_resolution_for_frequency_range(min_freq, max_freq, speed_of_sound, 6);
933
934 Self {
935 base_resolution: base,
936 source_refinement: 1.5,
937 source_refinement_radius: 0.5,
938 }
939 }
940
941 pub fn from_resolution(resolution: f64) -> Self {
943 Self {
944 base_resolution: resolution,
945 source_refinement: 1.0,
946 source_refinement_radius: 0.0,
947 }
948 }
949}
950
951#[cfg(test)]
952mod tests {
953 use super::*;
954 use ndarray::array;
955
956 #[test]
957 fn test_dense_operator() {
958 let matrix = array![
959 [Complex64::new(2.0, 0.0), Complex64::new(1.0, 0.0)],
960 [Complex64::new(1.0, 0.0), Complex64::new(3.0, 0.0)],
961 ];
962
963 let op = DenseOperator::new(matrix);
964 let x = array![Complex64::new(1.0, 0.0), Complex64::new(2.0, 0.0)];
965
966 let y = op.apply(&x);
967
968 assert!((y[0] - Complex64::new(4.0, 0.0)).norm() < 1e-10);
971 assert!((y[1] - Complex64::new(7.0, 0.0)).norm() < 1e-10);
972 }
973
974 #[test]
975 fn test_csr_operator() {
976 let matrix = CsrMatrix::from_triplets(
977 2,
978 2,
979 vec![
980 (0, 0, Complex64::new(2.0, 0.0)),
981 (0, 1, Complex64::new(1.0, 0.0)),
982 (1, 0, Complex64::new(1.0, 0.0)),
983 (1, 1, Complex64::new(3.0, 0.0)),
984 ],
985 );
986
987 let op = CsrOperator::new(matrix);
988 let x = array![Complex64::new(1.0, 0.0), Complex64::new(2.0, 0.0)];
989
990 let y = op.apply(&x);
991
992 assert!((y[0] - Complex64::new(4.0, 0.0)).norm() < 1e-10);
993 assert!((y[1] - Complex64::new(7.0, 0.0)).norm() < 1e-10);
994 }
995
996 #[test]
997 fn test_solve_cgs_with_operator() {
998 let matrix = array![
999 [Complex64::new(4.0, 0.0), Complex64::new(1.0, 0.0)],
1000 [Complex64::new(1.0, 0.0), Complex64::new(3.0, 0.0)],
1001 ];
1002
1003 let op = DenseOperator::new(matrix.clone());
1004 let b = array![Complex64::new(1.0, 0.0), Complex64::new(2.0, 0.0)];
1005
1006 let config = super::super::cgs::CgsConfig {
1007 max_iterations: 100,
1008 tolerance: 1e-10,
1009 print_interval: 0,
1010 };
1011
1012 let solution = solve_cgs(&op, &b, &config);
1013
1014 assert!(solution.converged);
1015
1016 let ax = matrix.dot(&solution.x);
1018 let error: f64 = (&ax - &b).iter().map(|e| e.norm_sqr()).sum::<f64>().sqrt();
1019 assert!(error < 1e-8);
1020 }
1021
1022 #[test]
1023 fn test_diagonal_preconditioner() {
1024 let diag = array![
1025 Complex64::new(2.0, 0.0),
1026 Complex64::new(4.0, 0.0),
1027 ];
1028
1029 let precond = DiagonalPreconditioner::from_diagonal(diag);
1030 let x = array![Complex64::new(1.0, 0.0), Complex64::new(2.0, 0.0)];
1031
1032 let y = precond.apply(&x);
1033
1034 assert!((y[0] - Complex64::new(0.5, 0.0)).norm() < 1e-10);
1036 assert!((y[1] - Complex64::new(0.5, 0.0)).norm() < 1e-10);
1037 }
1038}