1use ndarray::{Array1, Array2};
10use num_complex::Complex64;
11use std::f64::consts::PI;
12
13use crate::core::types::PhysicsParams;
14
15#[derive(Debug, Clone)]
17pub enum IncidentField {
18 PlaneWave {
20 direction: [f64; 3],
22 amplitude: Complex64,
24 },
25
26 PointSource {
28 position: [f64; 3],
30 strength: Complex64,
32 },
33
34 MultiplePlaneWaves(Vec<([f64; 3], Complex64)>),
36
37 MultiplePointSources(Vec<([f64; 3], Complex64)>),
39}
40
41impl IncidentField {
42 pub fn plane_wave_z() -> Self {
47 IncidentField::PlaneWave {
48 direction: [0.0, 0.0, 1.0],
49 amplitude: Complex64::new(1.0, 0.0),
50 }
51 }
52
53 pub fn plane_wave_neg_z() -> Self {
55 IncidentField::PlaneWave {
56 direction: [0.0, 0.0, -1.0],
57 amplitude: Complex64::new(1.0, 0.0),
58 }
59 }
60
61 pub fn plane_wave(direction: [f64; 3], amplitude: f64) -> Self {
63 let len = (direction[0].powi(2) + direction[1].powi(2) + direction[2].powi(2)).sqrt();
65 let dir = if len > 1e-10 {
66 [direction[0] / len, direction[1] / len, direction[2] / len]
67 } else {
68 [0.0, 0.0, -1.0]
69 };
70
71 IncidentField::PlaneWave {
72 direction: dir,
73 amplitude: Complex64::new(amplitude, 0.0),
74 }
75 }
76
77 pub fn point_source(position: [f64; 3], strength: f64) -> Self {
79 IncidentField::PointSource {
80 position,
81 strength: Complex64::new(strength, 0.0),
82 }
83 }
84
85 pub fn evaluate_pressure(
94 &self,
95 points: &Array2<f64>,
96 physics: &PhysicsParams,
97 ) -> Array1<Complex64> {
98 let n = points.nrows();
99 let k = physics.wave_number;
100 let mut pressure = Array1::zeros(n);
101
102 match self {
103 IncidentField::PlaneWave {
104 direction,
105 amplitude,
106 } => {
107 for i in 0..n {
108 let kdotx = k
110 * (direction[0] * points[[i, 0]]
111 + direction[1] * points[[i, 1]]
112 + direction[2] * points[[i, 2]]);
113
114 pressure[i] = *amplitude * Complex64::new(kdotx.cos(), kdotx.sin());
116 }
117 }
118
119 IncidentField::PointSource { position, strength } => {
120 for i in 0..n {
121 let dx = points[[i, 0]] - position[0];
122 let dy = points[[i, 1]] - position[1];
123 let dz = points[[i, 2]] - position[2];
124 let r = (dx * dx + dy * dy + dz * dz).sqrt();
125
126 if r > 1e-10 {
127 let kr = k * r;
128 let green = Complex64::new(kr.cos(), kr.sin()) / (4.0 * PI * r);
130 pressure[i] = *strength * green;
131 }
132 }
133 }
134
135 IncidentField::MultiplePlaneWaves(waves) => {
136 for (direction, amplitude) in waves {
137 for i in 0..n {
138 let kdotx = k
139 * (direction[0] * points[[i, 0]]
140 + direction[1] * points[[i, 1]]
141 + direction[2] * points[[i, 2]]);
142 pressure[i] += *amplitude * Complex64::new(kdotx.cos(), kdotx.sin());
143 }
144 }
145 }
146
147 IncidentField::MultiplePointSources(sources) => {
148 for (position, strength) in sources {
149 for i in 0..n {
150 let dx = points[[i, 0]] - position[0];
151 let dy = points[[i, 1]] - position[1];
152 let dz = points[[i, 2]] - position[2];
153 let r = (dx * dx + dy * dy + dz * dz).sqrt();
154
155 if r > 1e-10 {
156 let kr = k * r;
157 let green = Complex64::new(kr.cos(), kr.sin()) / (4.0 * PI * r);
158 pressure[i] += *strength * green;
159 }
160 }
161 }
162 }
163 }
164
165 pressure
166 }
167
168 pub fn evaluate_normal_derivative(
178 &self,
179 points: &Array2<f64>,
180 normals: &Array2<f64>,
181 physics: &PhysicsParams,
182 ) -> Array1<Complex64> {
183 let n = points.nrows();
184 let k = physics.wave_number;
185 let mut dpdn = Array1::zeros(n);
186
187 match self {
188 IncidentField::PlaneWave {
189 direction,
190 amplitude,
191 } => {
192 for i in 0..n {
193 let kdotx = k
194 * (direction[0] * points[[i, 0]]
195 + direction[1] * points[[i, 1]]
196 + direction[2] * points[[i, 2]]);
197
198 let kdotn = k
200 * (direction[0] * normals[[i, 0]]
201 + direction[1] * normals[[i, 1]]
202 + direction[2] * normals[[i, 2]]);
203
204 let p = *amplitude * Complex64::new(kdotx.cos(), kdotx.sin());
205 dpdn[i] = Complex64::new(0.0, kdotn) * p;
206 }
207 }
208
209 IncidentField::PointSource { position, strength } => {
210 for i in 0..n {
211 let dx = points[[i, 0]] - position[0];
212 let dy = points[[i, 1]] - position[1];
213 let dz = points[[i, 2]] - position[2];
214 let r = (dx * dx + dy * dy + dz * dz).sqrt();
215
216 if r > 1e-10 {
217 let kr = k * r;
218 let _r2 = r * r;
219
220 let exp_ikr = Complex64::new(kr.cos(), kr.sin());
222 let g = exp_ikr / (4.0 * PI * r);
223 let dgdr = (Complex64::new(0.0, k) - Complex64::new(1.0 / r, 0.0)) * g;
224
225 let drdn =
227 (dx * normals[[i, 0]] + dy * normals[[i, 1]] + dz * normals[[i, 2]])
228 / r;
229
230 dpdn[i] = *strength * dgdr * drdn;
231 }
232 }
233 }
234
235 IncidentField::MultiplePlaneWaves(waves) => {
236 for (direction, amplitude) in waves {
237 for i in 0..n {
238 let kdotx = k
239 * (direction[0] * points[[i, 0]]
240 + direction[1] * points[[i, 1]]
241 + direction[2] * points[[i, 2]]);
242
243 let kdotn = k
244 * (direction[0] * normals[[i, 0]]
245 + direction[1] * normals[[i, 1]]
246 + direction[2] * normals[[i, 2]]);
247
248 let p = *amplitude * Complex64::new(kdotx.cos(), kdotx.sin());
249 dpdn[i] += Complex64::new(0.0, kdotn) * p;
250 }
251 }
252 }
253
254 IncidentField::MultiplePointSources(sources) => {
255 for (position, strength) in sources {
256 for i in 0..n {
257 let dx = points[[i, 0]] - position[0];
258 let dy = points[[i, 1]] - position[1];
259 let dz = points[[i, 2]] - position[2];
260 let r = (dx * dx + dy * dy + dz * dz).sqrt();
261
262 if r > 1e-10 {
263 let kr = k * r;
264 let exp_ikr = Complex64::new(kr.cos(), kr.sin());
265 let g = exp_ikr / (4.0 * PI * r);
266 let dgdr = (Complex64::new(0.0, k) - Complex64::new(1.0 / r, 0.0)) * g;
267 let drdn = (dx * normals[[i, 0]]
268 + dy * normals[[i, 1]]
269 + dz * normals[[i, 2]])
270 / r;
271
272 dpdn[i] += *strength * dgdr * drdn;
273 }
274 }
275 }
276 }
277 }
278
279 dpdn
280 }
281
282 pub fn compute_rhs(
294 &self,
295 element_centers: &Array2<f64>,
296 element_normals: &Array2<f64>,
297 physics: &PhysicsParams,
298 use_burton_miller: bool,
299 ) -> Array1<Complex64> {
300 if use_burton_miller {
301 let beta = physics.burton_miller_beta();
302 self.compute_rhs_with_beta(element_centers, element_normals, physics, beta)
303 } else {
304 let gamma = Complex64::new(physics.gamma(), 0.0);
306 let p_inc = self.evaluate_pressure(element_centers, physics);
307 -gamma * p_inc
308 }
309 }
310
311 pub fn compute_rhs_with_beta(
318 &self,
319 element_centers: &Array2<f64>,
320 element_normals: &Array2<f64>,
321 physics: &PhysicsParams,
322 beta: Complex64,
323 ) -> Array1<Complex64> {
324 let p_inc = self.evaluate_pressure(element_centers, physics);
326
327 let dpdn = self.evaluate_normal_derivative(element_centers, element_normals, physics);
329
330 let gamma = Complex64::new(physics.gamma(), 0.0);
332 let tau = Complex64::new(physics.tau, 0.0);
333
334 let mut rhs = Array1::zeros(element_centers.nrows());
337 for i in 0..rhs.len() {
338 rhs[i] = -(gamma * p_inc[i] + beta * tau * dpdn[i]);
339 }
340
341 rhs
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 fn make_physics(k: f64) -> PhysicsParams {
350 let c = 343.0;
351 let freq = k * c / (2.0 * PI);
352 PhysicsParams::new(freq, c, 1.21, false)
353 }
354
355 #[test]
356 fn test_plane_wave_on_axis() {
357 let incident = IncidentField::plane_wave([0.0, 0.0, 1.0], 1.0);
358 let physics = make_physics(1.0);
359
360 let points =
362 Array2::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -1.0])
363 .unwrap();
364
365 let p = incident.evaluate_pressure(&points, &physics);
366
367 assert!((p[0].re - 1.0).abs() < 1e-10);
369 assert!(p[0].im.abs() < 1e-10);
370
371 assert!((p[1].re - (1.0_f64).cos()).abs() < 1e-10);
374 assert!((p[1].im - (1.0_f64).sin()).abs() < 1e-10);
375 }
376
377 #[test]
378 fn test_point_source_decay() {
379 let incident = IncidentField::point_source([0.0, 0.0, 0.0], 1.0);
380 let physics = make_physics(1.0);
381
382 let points =
384 Array2::from_shape_vec((3, 3), vec![1.0, 0.0, 0.0, 2.0, 0.0, 0.0, 4.0, 0.0, 0.0])
385 .unwrap();
386
387 let p = incident.evaluate_pressure(&points, &physics);
388
389 let ratio_1_2 = p[0].norm() / p[1].norm();
391 let ratio_2_4 = p[1].norm() / p[2].norm();
392
393 assert!((ratio_1_2 - 2.0).abs() < 0.1);
394 assert!((ratio_2_4 - 2.0).abs() < 0.1);
395 }
396
397 #[test]
398 fn test_plane_wave_normal_derivative() {
399 let incident = IncidentField::plane_wave([0.0, 0.0, 1.0], 1.0);
400 let physics = make_physics(1.0);
401
402 let points = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 0.0]).unwrap();
404 let normals = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 1.0]).unwrap();
405
406 let dpdn = incident.evaluate_normal_derivative(&points, &normals, &physics);
407
408 assert!(dpdn[0].re.abs() < 1e-10);
410 assert!((dpdn[0].im - 1.0).abs() < 1e-10);
411 }
412
413 #[test]
414 fn test_rhs_computation() {
415 let incident = IncidentField::plane_wave([0.0, 0.0, 1.0], 1.0);
416 let physics = make_physics(1.0);
417
418 let centers = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 1.0]).unwrap();
419 let normals = Array2::from_shape_vec((1, 3), vec![0.0, 0.0, 1.0]).unwrap();
420
421 let rhs = incident.compute_rhs(¢ers, &normals, &physics, false);
422
423 assert!(rhs[0].norm() > 0.0);
425 }
426}