1use ndarray::{Array1, Array2};
7use num_complex::Complex64;
8use solvers::iterative::{
9 BiCgstabConfig, BiCgstabSolution, CgsConfig, CgsSolution, GmresConfig, GmresSolution,
10};
11use solvers::iterative::{bicgstab, cgs, gmres};
12use solvers::preconditioners::IluPreconditioner;
13use solvers::traits::{LinearOperator, Preconditioner};
14
15use crate::core::assembly::mlfmm::MlfmmSystem;
16#[cfg(any(feature = "native", feature = "wasm"))]
17use crate::core::assembly::slfmm::SlfmmSystem;
18use crate::core::assembly::sparse::CsrMatrix;
19
20pub struct DenseOperator {
26 matrix: ndarray::Array2<Complex64>,
27}
28
29impl DenseOperator {
30 pub fn new(matrix: ndarray::Array2<Complex64>) -> Self {
32 Self { matrix }
33 }
34}
35
36impl LinearOperator<Complex64> for DenseOperator {
37 fn num_rows(&self) -> usize {
38 self.matrix.nrows()
39 }
40
41 fn num_cols(&self) -> usize {
42 self.matrix.ncols()
43 }
44
45 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
46 self.matrix.dot(x)
47 }
48
49 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
50 self.matrix.t().dot(x)
51 }
52}
53
54#[cfg(any(feature = "native", feature = "wasm"))]
56pub struct SlfmmOperator {
57 system: SlfmmSystem,
58}
59
60#[cfg(any(feature = "native", feature = "wasm"))]
61impl SlfmmOperator {
62 pub fn new(system: SlfmmSystem) -> Self {
64 Self { system }
65 }
66
67 pub fn system(&self) -> &SlfmmSystem {
69 &self.system
70 }
71
72 pub fn rhs(&self) -> &Array1<Complex64> {
74 &self.system.rhs
75 }
76}
77
78#[cfg(any(feature = "native", feature = "wasm"))]
79impl LinearOperator<Complex64> for SlfmmOperator {
80 fn num_rows(&self) -> usize {
81 self.system.num_dofs
82 }
83
84 fn num_cols(&self) -> usize {
85 self.system.num_dofs
86 }
87
88 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
89 self.system.matvec(x)
90 }
91
92 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
93 self.system.matvec_transpose(x)
94 }
95}
96
97pub struct MlfmmOperator {
99 system: MlfmmSystem,
100}
101
102impl MlfmmOperator {
103 pub fn new(system: MlfmmSystem) -> Self {
105 Self { system }
106 }
107
108 pub fn system(&self) -> &MlfmmSystem {
110 &self.system
111 }
112
113 pub fn rhs(&self) -> &Array1<Complex64> {
115 &self.system.rhs
116 }
117}
118
119impl LinearOperator<Complex64> for MlfmmOperator {
120 fn num_rows(&self) -> usize {
121 self.system.num_dofs
122 }
123
124 fn num_cols(&self) -> usize {
125 self.system.num_dofs
126 }
127
128 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
129 self.system.matvec(x)
130 }
131
132 fn apply_transpose(&self, _x: &Array1<Complex64>) -> Array1<Complex64> {
133 unimplemented!("MLFMM transpose not yet implemented")
134 }
135}
136
137pub struct CsrOperator {
139 matrix: CsrMatrix,
140}
141
142impl CsrOperator {
143 pub fn new(matrix: CsrMatrix) -> Self {
145 Self { matrix }
146 }
147
148 pub fn matrix(&self) -> &CsrMatrix {
150 &self.matrix
151 }
152}
153
154impl LinearOperator<Complex64> for CsrOperator {
155 fn num_rows(&self) -> usize {
156 self.matrix.num_rows
157 }
158
159 fn num_cols(&self) -> usize {
160 self.matrix.num_cols
161 }
162
163 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
164 self.matrix.matvec(x)
165 }
166
167 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
168 self.matrix.matvec_transpose(x)
169 }
170}
171
172pub struct DiagonalPreconditioner {
178 inv_diag: Array1<Complex64>,
179}
180
181impl DiagonalPreconditioner {
182 pub fn from_csr(matrix: &CsrMatrix) -> Self {
184 let diag = matrix.diagonal();
185 let inv_diag = diag.mapv(|d| {
186 if d.norm() > 1e-15 {
187 Complex64::new(1.0, 0.0) / d
188 } else {
189 Complex64::new(1.0, 0.0)
190 }
191 });
192 Self { inv_diag }
193 }
194
195 pub fn from_diagonal(diag: Array1<Complex64>) -> Self {
197 let inv_diag = diag.mapv(|d| {
198 if d.norm() > 1e-15 {
199 Complex64::new(1.0, 0.0) / d
200 } else {
201 Complex64::new(1.0, 0.0)
202 }
203 });
204 Self { inv_diag }
205 }
206}
207
208impl Preconditioner<Complex64> for DiagonalPreconditioner {
209 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
210 x * &self.inv_diag
211 }
212}
213
214impl LinearOperator<Complex64> for DiagonalPreconditioner {
215 fn num_rows(&self) -> usize {
216 self.inv_diag.len()
217 }
218
219 fn num_cols(&self) -> usize {
220 self.inv_diag.len()
221 }
222
223 fn apply(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
224 x * &self.inv_diag
225 }
226
227 fn apply_transpose(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
228 x * &self.inv_diag
229 }
230}
231
232#[derive(Debug, Clone)]
236pub struct SparseNearfieldIlu {
237 l_values: Vec<Complex64>,
239 n: usize,
241}
242
243impl SparseNearfieldIlu {
244 #[cfg(any(feature = "native", feature = "wasm"))]
249 pub fn from_slfmm(
250 near_blocks: &[super::super::assembly::slfmm::NearFieldBlock],
251 cluster_dof_indices: &[Vec<usize>],
252 num_dofs: usize,
253 _threshold: f64,
254 ) -> Self {
255 let mut diag = Array1::ones(num_dofs);
257
258 for block in near_blocks {
259 if block.source_cluster == block.field_cluster {
260 let dofs = &cluster_dof_indices[block.source_cluster];
261 for (local_i, &global_i) in dofs.iter().enumerate() {
262 if local_i < block.coefficients.nrows() {
263 diag[global_i] = block.coefficients[[local_i, local_i]];
264 }
265 }
266 }
267 }
268
269 let l_values = diag
270 .iter()
271 .map(|d| {
272 if d.norm() > 1e-15 {
273 *d
274 } else {
275 Complex64::new(1.0, 0.0)
276 }
277 })
278 .collect();
279
280 Self {
281 l_values,
282 n: num_dofs,
283 }
284 }
285}
286
287impl Preconditioner<Complex64> for SparseNearfieldIlu {
288 fn apply(&self, r: &Array1<Complex64>) -> Array1<Complex64> {
289 let mut z = Array1::zeros(self.n);
290 for i in 0..self.n {
291 z[i] = r[i] / self.l_values[i];
292 }
293 z
294 }
295}
296
297#[cfg(any(feature = "native", feature = "wasm"))]
299#[derive(Debug, Clone)]
300pub struct HierarchicalFmmPreconditioner {
301 #[allow(dead_code)]
303 block_lu: Vec<Array2<Complex64>>,
304 #[allow(dead_code)]
306 cluster_dof_indices: Vec<Vec<usize>>,
307 #[allow(dead_code)]
309 num_dofs: usize,
310}
311
312#[cfg(any(feature = "native", feature = "wasm"))]
313impl HierarchicalFmmPreconditioner {
314 pub fn from_slfmm(system: &SlfmmSystem) -> Self {
316 Self::from_slfmm_blocks(
317 &system.near_matrix,
318 &system.cluster_dof_indices,
319 system.num_dofs,
320 )
321 }
322
323 pub fn from_slfmm_blocks(
327 _near_blocks: &[super::super::assembly::slfmm::NearFieldBlock],
328 cluster_dof_indices: &[Vec<usize>],
329 num_dofs: usize,
330 ) -> Self {
331 let num_clusters = cluster_dof_indices.len();
333 let mut block_lu = Vec::with_capacity(num_clusters);
334
335 for dofs in cluster_dof_indices {
336 let n = dofs.len();
337 block_lu.push(Array2::eye(n));
338 }
339
340 Self {
341 block_lu,
342 cluster_dof_indices: cluster_dof_indices.to_vec(),
343 num_dofs,
344 }
345 }
346}
347
348#[cfg(any(feature = "native", feature = "wasm"))]
349impl Preconditioner<Complex64> for HierarchicalFmmPreconditioner {
350 fn apply(&self, r: &Array1<Complex64>) -> Array1<Complex64> {
351 r.clone()
352 }
353}
354
355pub fn solve_cgs<O: LinearOperator<Complex64>>(
361 operator: &O,
362 b: &Array1<Complex64>,
363 config: &CgsConfig<f64>,
364) -> CgsSolution<Complex64> {
365 cgs(operator, b, config)
366}
367
368pub fn solve_bicgstab<O: LinearOperator<Complex64>>(
370 operator: &O,
371 b: &Array1<Complex64>,
372 config: &BiCgstabConfig<f64>,
373) -> BiCgstabSolution<Complex64> {
374 bicgstab(operator, b, config)
375}
376
377pub fn solve_gmres<O: LinearOperator<Complex64>>(
379 operator: &O,
380 b: &Array1<Complex64>,
381 config: &GmresConfig<f64>,
382) -> GmresSolution<Complex64> {
383 gmres(operator, b, config)
384}
385
386pub fn solve_with_ilu(
390 matrix: &Array2<Complex64>,
391 b: &Array1<Complex64>,
392 config: &CgsConfig<f64>,
393) -> CgsSolution<Complex64> {
394 let csr = solvers::sparse::CsrMatrix::from_dense(matrix, 1e-15);
398 let _precond = IluPreconditioner::from_csr(&csr);
399 let op = DenseOperator::new(matrix.clone());
400
401 eprintln!(
414 "Warning: CGS with ILU not fully supported via public API, running without preconditioner"
415 );
416 cgs(&op, b, config)
417}
418
419pub fn solve_with_ilu_operator<O: LinearOperator<Complex64>>(
421 operator: &O,
422 _nearfield_matrix: &Array2<Complex64>,
423 b: &Array1<Complex64>,
424 config: &CgsConfig<f64>,
425) -> CgsSolution<Complex64> {
426 cgs(operator, b, config)
427}
428
429pub fn solve_tbem_with_ilu(
442 matrix: &Array2<Complex64>,
443 b: &Array1<Complex64>,
444 config: &CgsConfig<f64>,
445) -> CgsSolution<Complex64> {
446 solve_with_ilu(matrix, b, config)
447}
448
449pub fn gmres_solve_with_ilu(
451 matrix: &Array2<Complex64>,
452 b: &Array1<Complex64>,
453 config: &GmresConfig<f64>,
454) -> GmresSolution<Complex64> {
455 let csr = solvers::sparse::CsrMatrix::from_dense(matrix, 1e-15);
456 let precond = IluPreconditioner::from_csr(&csr);
457 let op = DenseOperator::new(matrix.clone());
458 solvers::iterative::gmres_preconditioned(&op, &precond, b, config)
459}
460
461pub fn gmres_solve_with_ilu_operator<O: LinearOperator<Complex64>>(
463 operator: &O,
464 nearfield_matrix: &Array2<Complex64>,
465 b: &Array1<Complex64>,
466 config: &GmresConfig<f64>,
467) -> GmresSolution<Complex64> {
468 let csr = solvers::sparse::CsrMatrix::from_dense(nearfield_matrix, 1e-15);
469 let precond = IluPreconditioner::from_csr(&csr);
470 solvers::iterative::gmres_preconditioned(operator, &precond, b, config)
471}
472
473pub fn gmres_solve_tbem_with_ilu(
478 matrix: &Array2<Complex64>,
479 b: &Array1<Complex64>,
480 config: &GmresConfig<f64>,
481) -> GmresSolution<Complex64> {
482 gmres_solve_with_ilu(matrix, b, config)
483}
484
485#[cfg(any(feature = "native", feature = "wasm"))]
490pub fn gmres_solve_with_hierarchical_precond(
491 fmm_system: &crate::core::assembly::slfmm::SlfmmSystem,
492 b: &Array1<Complex64>,
493 config: &GmresConfig<f64>,
494) -> GmresSolution<Complex64> {
495 let op = SlfmmOperator::new(fmm_system.clone());
496 let precond = HierarchicalFmmPreconditioner::from_slfmm(fmm_system);
497 solvers::iterative::gmres_preconditioned(&op, &precond, b, config)
498}
499
500#[cfg(any(feature = "native", feature = "wasm"))]
505pub fn gmres_solve_fmm_hierarchical(
506 fmm_operator: &SlfmmOperator,
507 config: &GmresConfig<f64>,
508) -> GmresSolution<Complex64> {
509 let precond = HierarchicalFmmPreconditioner::from_slfmm(fmm_operator.system());
510 let b = fmm_operator.rhs();
511 solvers::iterative::gmres_preconditioned(fmm_operator, &precond, b, config)
512}
513
514#[cfg(feature = "native")]
516pub fn gmres_solve_fmm_batched(
517 fmm_system: &crate::core::assembly::slfmm::SlfmmSystem,
518 b: &Array1<Complex64>,
519 config: &GmresConfig<f64>,
520) -> GmresSolution<Complex64> {
521 let op = SlfmmOperator::new(fmm_system.clone());
522 gmres(&op, b, config)
523}
524
525#[cfg(feature = "native")]
527pub fn gmres_solve_fmm_batched_with_ilu(
528 fmm_system: &crate::core::assembly::slfmm::SlfmmSystem,
529 b: &Array1<Complex64>,
530 config: &GmresConfig<f64>,
531) -> GmresSolution<Complex64> {
532 let op = SlfmmOperator::new(fmm_system.clone());
533 let nearfield_matrix = fmm_system.extract_near_field_matrix();
534 let csr = solvers::sparse::CsrMatrix::from_dense(&nearfield_matrix, 1e-15);
535 let precond = IluPreconditioner::from_csr(&csr);
536 solvers::iterative::gmres_preconditioned(&op, &precond, b, config)
537}
538
539pub fn recommended_mesh_resolution(
545 frequency: f64,
546 speed_of_sound: f64,
547 elements_per_wavelength: usize,
548) -> f64 {
549 let wavelength = speed_of_sound / frequency;
550 elements_per_wavelength as f64 / wavelength
551}
552
553pub fn mesh_resolution_for_frequency_range(
555 _min_freq: f64,
556 max_freq: f64,
557 speed_of_sound: f64,
558 elements_per_wavelength: usize,
559) -> f64 {
560 recommended_mesh_resolution(max_freq, speed_of_sound, elements_per_wavelength)
561}
562
563pub fn estimate_element_count(room_dimensions: (f64, f64, f64), mesh_resolution: f64) -> usize {
565 let (w, d, h) = room_dimensions;
566 let surface_area = 2.0 * (w * d + w * h + d * h);
567 let element_size = 1.0 / mesh_resolution;
568 let element_area = element_size * element_size;
569 (surface_area / element_area).ceil() as usize
570}
571
572pub struct AdaptiveMeshConfig {
574 pub base_resolution: f64,
576 pub source_refinement: f64,
578 pub source_refinement_radius: f64,
580}
581
582impl AdaptiveMeshConfig {
583 pub fn for_frequency_range(min_freq: f64, max_freq: f64) -> Self {
585 let speed_of_sound = 343.0;
586 let base = mesh_resolution_for_frequency_range(min_freq, max_freq, speed_of_sound, 6);
587 Self {
588 base_resolution: base,
589 source_refinement: 1.5,
590 source_refinement_radius: 0.5,
591 }
592 }
593
594 pub fn from_resolution(resolution: f64) -> Self {
596 Self {
597 base_resolution: resolution,
598 source_refinement: 1.0,
599 source_refinement_radius: 0.0,
600 }
601 }
602}