1use ndarray::{Array1, Array2};
32use num_complex::Complex64;
33use std::f64::consts::PI;
34
35use crate::core::assembly::tbem::build_tbem_system_with_beta;
36use crate::core::incident::IncidentField;
37use crate::core::mesh::generators::{generate_icosphere_mesh, generate_sphere_mesh};
38use crate::core::postprocess::pressure::{compute_total_field, FieldPoint};
39use crate::core::solver::direct::direct_solve;
40use crate::core::types::{BoundaryCondition, Element, Mesh, PhysicsParams};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum SolverMethod {
45 Direct,
47 Cgs,
49 BiCgStab,
51}
52
53impl Default for SolverMethod {
54 fn default() -> Self {
55 SolverMethod::Direct
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum AssemblyMethod {
62 Tbem,
64 Slfmm,
66 Mlfmm,
68}
69
70impl Default for AssemblyMethod {
71 fn default() -> Self {
72 AssemblyMethod::Tbem
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum BoundaryConditionType {
79 Rigid,
81 Soft,
83 Impedance,
85}
86
87#[derive(Debug, Clone)]
89pub struct BemProblem {
90 pub mesh: Mesh,
92 pub physics: PhysicsParams,
94 pub incident_field: IncidentField,
96 pub bc_type: BoundaryConditionType,
98 pub use_burton_miller: bool,
100}
101
102impl BemProblem {
103 pub fn rigid_sphere_scattering(
111 radius: f64,
112 frequency: f64,
113 speed_of_sound: f64,
114 density: f64,
115 ) -> Self {
116 let k = 2.0 * PI * frequency / speed_of_sound;
118 let ka = k * radius;
119
120 let subdivisions = if ka < 1.0 {
123 2 } else if ka < 5.0 {
125 3 } else {
127 4 };
129
130 let mesh = generate_icosphere_mesh(radius, subdivisions);
131 let physics = PhysicsParams::new(frequency, speed_of_sound, density, false);
132 let incident_field = IncidentField::plane_wave_z();
133
134 Self {
135 mesh,
136 physics,
137 incident_field,
138 bc_type: BoundaryConditionType::Rigid,
139 use_burton_miller: true,
140 }
141 }
142
143 pub fn rigid_sphere_scattering_custom(
145 radius: f64,
146 frequency: f64,
147 speed_of_sound: f64,
148 density: f64,
149 n_theta: usize,
150 n_phi: usize,
151 ) -> Self {
152 let mesh = generate_sphere_mesh(radius, n_theta, n_phi);
153 let physics = PhysicsParams::new(frequency, speed_of_sound, density, false);
154 let incident_field = IncidentField::plane_wave_z();
155
156 Self {
157 mesh,
158 physics,
159 incident_field,
160 bc_type: BoundaryConditionType::Rigid,
161 use_burton_miller: true,
162 }
163 }
164
165 pub fn with_incident_field(mut self, field: IncidentField) -> Self {
167 self.incident_field = field;
168 self
169 }
170
171 pub fn with_boundary_condition(mut self, bc_type: BoundaryConditionType) -> Self {
173 self.bc_type = bc_type;
174 self
175 }
176
177 pub fn with_burton_miller(mut self, use_bm: bool) -> Self {
179 self.use_burton_miller = use_bm;
180 self
181 }
182
183 pub fn ka(&self) -> f64 {
185 self.physics.wave_number * self.mesh_radius()
186 }
187
188 fn mesh_radius(&self) -> f64 {
190 let mut max_r = 0.0f64;
192 for i in 0..self.mesh.nodes.nrows() {
193 let r = (self.mesh.nodes[[i, 0]].powi(2)
194 + self.mesh.nodes[[i, 1]].powi(2)
195 + self.mesh.nodes[[i, 2]].powi(2))
196 .sqrt();
197 max_r = max_r.max(r);
198 }
199 max_r
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct BemSolver {
206 pub solver_method: SolverMethod,
208 pub assembly_method: AssemblyMethod,
210 pub max_iterations: usize,
212 pub tolerance: f64,
214 pub verbose: bool,
216 pub beta_scale: f64,
218}
219
220impl Default for BemSolver {
221 fn default() -> Self {
222 Self {
223 solver_method: SolverMethod::Direct,
224 assembly_method: AssemblyMethod::Tbem,
225 max_iterations: 1000,
226 tolerance: 1e-8,
227 verbose: false,
228 beta_scale: 4.0, }
230 }
231}
232
233impl BemSolver {
234 pub fn new() -> Self {
236 Self::default()
237 }
238
239 pub fn with_solver_method(mut self, method: SolverMethod) -> Self {
241 self.solver_method = method;
242 self
243 }
244
245 pub fn with_assembly_method(mut self, method: AssemblyMethod) -> Self {
247 self.assembly_method = method;
248 self
249 }
250
251 pub fn with_max_iterations(mut self, max_iter: usize) -> Self {
253 self.max_iterations = max_iter;
254 self
255 }
256
257 pub fn with_tolerance(mut self, tol: f64) -> Self {
259 self.tolerance = tol;
260 self
261 }
262
263 pub fn with_verbose(mut self, verbose: bool) -> Self {
265 self.verbose = verbose;
266 self
267 }
268
269 pub fn solve(&self, problem: &BemProblem) -> Result<BemSolution, BemError> {
277 if self.verbose {
278 log::info!(
279 "Solving BEM problem: {} elements, ka = {:.3}",
280 problem.mesh.elements.len(),
281 problem.ka()
282 );
283 }
284
285 let elements = self.prepare_elements(problem);
287
288 let (matrix, rhs) = self.assemble_system(&elements, &problem.mesh.nodes, &problem.physics)?;
290
291 let rhs = self.add_incident_field_rhs(
293 rhs,
294 &elements,
295 &problem.incident_field,
296 &problem.physics,
297 problem.use_burton_miller,
298 );
299
300 let surface_pressure = self.solve_linear_system(&matrix, &rhs)?;
302
303 if self.verbose {
304 log::info!("Solution complete. Max surface pressure: {:.6}",
305 surface_pressure.iter().map(|p| p.norm()).fold(0.0f64, f64::max));
306 }
307
308 Ok(BemSolution {
309 surface_pressure,
310 elements,
311 nodes: problem.mesh.nodes.clone(),
312 incident_field: problem.incident_field.clone(),
313 physics: problem.physics.clone(),
314 })
315 }
316
317 fn prepare_elements(&self, problem: &BemProblem) -> Vec<Element> {
319 let mut elements = problem.mesh.elements.clone();
320
321 let bc = match problem.bc_type {
323 BoundaryConditionType::Rigid => {
324 BoundaryCondition::Velocity(vec![Complex64::new(0.0, 0.0)])
326 }
327 BoundaryConditionType::Soft => {
328 BoundaryCondition::Pressure(vec![Complex64::new(0.0, 0.0)])
330 }
331 BoundaryConditionType::Impedance => {
332 let z0 = problem.physics.density * problem.physics.speed_of_sound;
334 BoundaryCondition::VelocityWithAdmittance {
335 velocity: vec![Complex64::new(0.0, 0.0)],
336 admittance: Complex64::new(1.0 / z0, 0.0),
337 }
338 }
339 };
340
341 for (i, elem) in elements.iter_mut().enumerate() {
343 elem.boundary_condition = bc.clone();
344 elem.dof_addresses = vec![i];
345 }
346
347 elements
348 }
349
350 fn assemble_system(
352 &self,
353 elements: &[Element],
354 nodes: &Array2<f64>,
355 physics: &PhysicsParams,
356 ) -> Result<(Array2<Complex64>, Array1<Complex64>), BemError> {
357 match self.assembly_method {
358 AssemblyMethod::Tbem => {
359 let beta = physics.burton_miller_beta_scaled(self.beta_scale);
361 let system = build_tbem_system_with_beta(elements, nodes, physics, beta);
362 Ok((system.matrix, system.rhs))
363 }
364 AssemblyMethod::Slfmm | AssemblyMethod::Mlfmm => {
365 Err(BemError::NotImplemented(
367 "FMM assembly not yet available in high-level API".to_string(),
368 ))
369 }
370 }
371 }
372
373 fn add_incident_field_rhs(
375 &self,
376 mut rhs: Array1<Complex64>,
377 elements: &[Element],
378 incident_field: &IncidentField,
379 physics: &PhysicsParams,
380 use_burton_miller: bool,
381 ) -> Array1<Complex64> {
382 let n = elements.len();
384 let mut centers = Array2::zeros((n, 3));
385 let mut normals = Array2::zeros((n, 3));
386
387 for (i, elem) in elements.iter().enumerate() {
388 for j in 0..3 {
389 centers[[i, j]] = elem.center[j];
390 normals[[i, j]] = elem.normal[j];
391 }
392 }
393
394 let incident_rhs = if use_burton_miller {
396 let beta = physics.burton_miller_beta_scaled(self.beta_scale);
397 incident_field.compute_rhs_with_beta(¢ers, &normals, physics, beta)
398 } else {
399 incident_field.compute_rhs(¢ers, &normals, physics, false)
400 };
401
402 rhs = rhs + incident_rhs;
404
405 rhs
406 }
407
408 fn solve_linear_system(
410 &self,
411 matrix: &Array2<Complex64>,
412 rhs: &Array1<Complex64>,
413 ) -> Result<Array1<Complex64>, BemError> {
414 match self.solver_method {
415 SolverMethod::Direct => {
416 let solution = direct_solve(matrix, rhs);
417 if solution.success {
418 Ok(solution.x)
419 } else {
420 Err(BemError::SolverFailed("LU decomposition failed".to_string()))
421 }
422 }
423 SolverMethod::Cgs | SolverMethod::BiCgStab => {
424 Err(BemError::NotImplemented(
426 "Iterative solvers not yet available in high-level API".to_string(),
427 ))
428 }
429 }
430 }
431}
432
433#[derive(Debug, Clone)]
435pub struct BemSolution {
436 pub surface_pressure: Array1<Complex64>,
438 pub elements: Vec<Element>,
440 pub nodes: Array2<f64>,
442 pub incident_field: IncidentField,
444 pub physics: PhysicsParams,
446}
447
448impl BemSolution {
449 pub fn evaluate_pressure(&self, point: &[f64; 3]) -> Complex64 {
451 let eval_points = Array2::from_shape_vec((1, 3), vec![point[0], point[1], point[2]]).unwrap();
452
453 let field_points = compute_total_field(
454 &eval_points,
455 &self.elements,
456 &self.nodes,
457 &self.surface_pressure,
458 None,
459 &self.incident_field,
460 &self.physics,
461 );
462
463 field_points[0].p_total
464 }
465
466 pub fn evaluate_pressure_field(&self, points: &Array2<f64>) -> Vec<FieldPoint> {
468 compute_total_field(
469 points,
470 &self.elements,
471 &self.nodes,
472 &self.surface_pressure,
473 None,
474 &self.incident_field,
475 &self.physics,
476 )
477 }
478
479 pub fn max_surface_pressure(&self) -> f64 {
481 self.surface_pressure
482 .iter()
483 .map(|p| p.norm())
484 .fold(0.0f64, f64::max)
485 }
486
487 pub fn mean_surface_pressure(&self) -> f64 {
489 let sum: f64 = self.surface_pressure.iter().map(|p| p.norm()).sum();
490 sum / self.surface_pressure.len() as f64
491 }
492
493 pub fn num_dofs(&self) -> usize {
495 self.surface_pressure.len()
496 }
497}
498
499#[derive(Debug, Clone)]
501pub enum BemError {
502 NotImplemented(String),
504 SolverFailed(String),
506 InvalidMesh(String),
508 InvalidParameters(String),
510}
511
512impl std::fmt::Display for BemError {
513 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
514 match self {
515 BemError::NotImplemented(msg) => write!(f, "Not implemented: {}", msg),
516 BemError::SolverFailed(msg) => write!(f, "Solver failed: {}", msg),
517 BemError::InvalidMesh(msg) => write!(f, "Invalid mesh: {}", msg),
518 BemError::InvalidParameters(msg) => write!(f, "Invalid parameters: {}", msg),
519 }
520 }
521}
522
523impl std::error::Error for BemError {}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_bem_problem_creation() {
531 let problem = BemProblem::rigid_sphere_scattering(0.1, 1000.0, 343.0, 1.21);
532
533 assert!(problem.mesh.elements.len() > 0);
534 assert!(problem.mesh.nodes.nrows() > 0);
535 assert!(problem.ka() > 0.0);
536 }
537
538 #[test]
539 fn test_bem_solver_creation() {
540 let solver = BemSolver::new()
541 .with_solver_method(SolverMethod::Direct)
542 .with_assembly_method(AssemblyMethod::Tbem)
543 .with_verbose(false);
544
545 assert_eq!(solver.solver_method, SolverMethod::Direct);
546 assert_eq!(solver.assembly_method, AssemblyMethod::Tbem);
547 }
548
549 #[test]
550 fn test_bem_solver_small_problem() {
551 let problem = BemProblem::rigid_sphere_scattering_custom(
553 0.1, 100.0, 343.0,
556 1.21,
557 4, 8,
559 );
560
561 let solver = BemSolver::new();
562 let result = solver.solve(&problem);
563
564 assert!(result.is_ok());
565 let solution = result.unwrap();
566 assert!(solution.num_dofs() > 0);
567 assert!(solution.max_surface_pressure() > 0.0);
568 }
569
570 #[test]
571 fn test_field_evaluation() {
572 let problem = BemProblem::rigid_sphere_scattering_custom(
574 0.1,
575 100.0,
576 343.0,
577 1.21,
578 4,
579 8,
580 );
581
582 let solver = BemSolver::new();
583 let solution = solver.solve(&problem).unwrap();
584
585 let p = solution.evaluate_pressure(&[0.0, 0.0, 0.2]);
587
588 assert!(p.norm() > 0.0);
590 }
591}