bem/analytical/
mod.rs

1//! Analytical solutions for BEM validation
2//!
3//! This module provides exact solutions to acoustic scattering problems
4//! used to validate BEM implementations.
5//!
6//! ## Available Solutions
7//!
8//! - **1D**: Plane wave propagation
9//! - **2D**: Cylinder scattering (Bessel/Hankel series)
10//! - **3D**: Sphere scattering (Mie theory)
11
12use num_complex::Complex64;
13use serde::{Deserialize, Serialize};
14
15pub mod solutions_1d;
16pub mod solutions_2d;
17pub mod solutions_3d;
18
19pub use solutions_1d::*;
20pub use solutions_2d::*;
21pub use solutions_3d::*;
22
23/// Point in space (1D, 2D, or 3D)
24#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
25pub struct Point {
26    /// x-coordinate
27    pub x: f64,
28    /// y-coordinate (0 for 1D)
29    pub y: f64,
30    /// z-coordinate (0 for 1D/2D)
31    pub z: f64,
32}
33
34impl Point {
35    /// Create 1D point
36    pub fn new_1d(x: f64) -> Self {
37        Self { x, y: 0.0, z: 0.0 }
38    }
39
40    /// Create 2D point
41    pub fn new_2d(x: f64, y: f64) -> Self {
42        Self { x, y, z: 0.0 }
43    }
44
45    /// Create 3D point
46    pub fn new_3d(x: f64, y: f64, z: f64) -> Self {
47        Self { x, y, z }
48    }
49
50    /// Polar coordinates (r, θ) for 2D
51    pub fn from_polar(r: f64, theta: f64) -> Self {
52        Self::new_2d(r * theta.cos(), r * theta.sin())
53    }
54
55    /// Spherical coordinates (r, θ, φ) for 3D
56    pub fn from_spherical(r: f64, theta: f64, phi: f64) -> Self {
57        Self::new_3d(
58            r * theta.sin() * phi.cos(),
59            r * theta.sin() * phi.sin(),
60            r * theta.cos(),
61        )
62    }
63
64    /// Distance from origin
65    pub fn radius(&self) -> f64 {
66        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
67    }
68
69    /// Polar angle (2D)
70    pub fn theta_2d(&self) -> f64 {
71        self.y.atan2(self.x)
72    }
73
74    /// Spherical polar angle (3D, from z-axis)
75    pub fn theta_3d(&self) -> f64 {
76        (self.z / self.radius()).acos()
77    }
78
79    /// Spherical azimuthal angle (3D)
80    pub fn phi_3d(&self) -> f64 {
81        self.y.atan2(self.x)
82    }
83}
84
85/// Analytical solution result
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct AnalyticalSolution {
88    /// Test name
89    pub name: String,
90    /// Dimensionality (1, 2, or 3)
91    pub dimensions: usize,
92    /// Evaluation points
93    pub positions: Vec<Point>,
94    /// Complex pressure values
95    pub pressure: Vec<Complex64>,
96    /// Wave number
97    pub wave_number: f64,
98    /// Frequency (Hz)
99    pub frequency: f64,
100    /// Additional metadata
101    pub metadata: serde_json::Value,
102}
103
104impl AnalyticalSolution {
105    /// Compute pressure magnitude
106    pub fn magnitude(&self) -> Vec<f64> {
107        self.pressure.iter().map(|p| p.norm()).collect()
108    }
109
110    /// Compute pressure phase (radians)
111    pub fn phase(&self) -> Vec<f64> {
112        self.pressure.iter().map(|p| p.arg()).collect()
113    }
114
115    /// Real part of pressure
116    pub fn real(&self) -> Vec<f64> {
117        self.pressure.iter().map(|p| p.re).collect()
118    }
119
120    /// Imaginary part of pressure
121    pub fn imag(&self) -> Vec<f64> {
122        self.pressure.iter().map(|p| p.im).collect()
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_point_creation() {
132        let p1d = Point::new_1d(1.0);
133        assert_eq!(p1d.x, 1.0);
134        assert_eq!(p1d.y, 0.0);
135        assert_eq!(p1d.z, 0.0);
136
137        let p2d = Point::from_polar(1.0, std::f64::consts::PI / 4.0);
138        assert!((p2d.radius() - 1.0).abs() < 1e-10);
139
140        let p3d = Point::from_spherical(1.0, std::f64::consts::PI / 2.0, 0.0);
141        assert!((p3d.radius() - 1.0).abs() < 1e-10);
142    }
143}