1use ndarray::{Array1, Array2};
14use num_complex::Complex64;
15use std::f64::consts::PI;
16
17use crate::core::incident::IncidentField;
19use crate::core::integration::gauss::triangle_quadrature;
20use crate::core::types::{Element, ElementType, PhysicsParams};
21
22#[derive(Debug, Clone)]
24pub struct FieldPoint {
25 pub position: [f64; 3],
27 pub p_incident: Complex64,
29 pub p_scattered: Complex64,
31 pub p_total: Complex64,
33}
34
35impl FieldPoint {
36 pub fn new(position: [f64; 3], p_incident: Complex64, p_scattered: Complex64) -> Self {
38 Self {
39 position,
40 p_incident,
41 p_scattered,
42 p_total: p_incident + p_scattered,
43 }
44 }
45
46 pub fn spl_db(&self) -> f64 {
48 let p_ref = 20e-6; 20.0 * (self.p_total.norm() / p_ref).log10()
50 }
51
52 pub fn magnitude(&self) -> f64 {
54 self.p_total.norm()
55 }
56
57 pub fn phase(&self) -> f64 {
59 self.p_total.arg()
60 }
61}
62
63pub fn compute_scattered_field(
82 eval_points: &Array2<f64>,
83 elements: &[Element],
84 nodes: &Array2<f64>,
85 surface_pressure: &Array1<Complex64>,
86 surface_velocity: Option<&Array1<Complex64>>,
87 physics: &PhysicsParams,
88) -> Array1<Complex64> {
89 let n_eval = eval_points.nrows();
90 let k = physics.wave_number;
91 let harmonic_factor = physics.harmonic_factor;
92 let wavruim = k * harmonic_factor;
93
94 let mut p_scattered = Array1::zeros(n_eval);
95
96 let boundary_elements: Vec<_> = elements
98 .iter()
99 .filter(|e| !e.property.is_evaluation())
100 .collect();
101
102 for i in 0..n_eval {
103 let x = Array1::from_vec(vec![
104 eval_points[[i, 0]],
105 eval_points[[i, 1]],
106 eval_points[[i, 2]],
107 ]);
108
109 let mut p_scat = Complex64::new(0.0, 0.0);
110
111 for (j, elem) in boundary_elements.iter().enumerate() {
112 let p_surf = surface_pressure[j];
114 let v_surf = surface_velocity.map_or(Complex64::new(0.0, 0.0), |v| v[j]);
115
116 let elem_coords = get_element_coords(elem, nodes);
118
119 let contribution = integrate_element_field(
121 &x,
122 &elem_coords,
123 elem.element_type,
124 p_surf,
125 v_surf,
126 k,
127 wavruim,
128 );
129
130 p_scat += contribution;
131 }
132
133 p_scattered[i] = p_scat;
134 }
135
136 p_scattered
137}
138
139fn get_element_coords(elem: &Element, nodes: &Array2<f64>) -> Array2<f64> {
141 let n_nodes = elem.connectivity.len();
142 let mut coords = Array2::zeros((n_nodes, 3));
143 for (i, &node_idx) in elem.connectivity.iter().enumerate() {
144 for j in 0..3 {
145 coords[[i, j]] = nodes[[node_idx, j]];
146 }
147 }
148 coords
149}
150
151fn integrate_element_field(
155 x: &Array1<f64>,
156 elem_coords: &Array2<f64>,
157 elem_type: ElementType,
158 p_surf: Complex64,
159 v_surf: Complex64,
160 _k: f64,
161 wavruim: f64,
162) -> Complex64 {
163 let mut result = Complex64::new(0.0, 0.0);
164
165 match elem_type {
166 ElementType::Tri3 => {
167 let gauss_order = 3;
169 let quad_points = triangle_quadrature(gauss_order);
170
171 for (xi, eta, weight) in quad_points {
172 let shape_fn = [1.0 - xi - eta, xi, eta];
174 let shape_ds = [-1.0, 1.0, 0.0];
175 let shape_dt = [-1.0, 0.0, 1.0];
176
177 result += integrate_quad_point(
178 x,
179 elem_coords,
180 &shape_fn,
181 &shape_ds,
182 &shape_dt,
183 weight,
184 p_surf,
185 v_surf,
186 wavruim,
187 3,
188 );
189 }
190 }
191 ElementType::Quad4 => {
192 let g = 0.5773502691896257_f64;
195 let gauss_pts = [(-g, -g), (g, -g), (g, g), (-g, g)];
196 let weight = 1.0; for (xi, eta) in gauss_pts {
199 let shape_fn = [
201 0.25 * (1.0 - xi) * (1.0 - eta),
202 0.25 * (1.0 + xi) * (1.0 - eta),
203 0.25 * (1.0 + xi) * (1.0 + eta),
204 0.25 * (1.0 - xi) * (1.0 + eta),
205 ];
206 let shape_ds = [
207 -0.25 * (1.0 - eta),
208 0.25 * (1.0 - eta),
209 0.25 * (1.0 + eta),
210 -0.25 * (1.0 + eta),
211 ];
212 let shape_dt = [
213 -0.25 * (1.0 - xi),
214 -0.25 * (1.0 + xi),
215 0.25 * (1.0 + xi),
216 0.25 * (1.0 - xi),
217 ];
218
219 result += integrate_quad_point(
220 x,
221 elem_coords,
222 &shape_fn,
223 &shape_ds,
224 &shape_dt,
225 weight,
226 p_surf,
227 v_surf,
228 wavruim,
229 4,
230 );
231 }
232 }
233 }
234
235 result
236}
237
238#[inline]
240fn integrate_quad_point(
241 x: &Array1<f64>,
242 elem_coords: &Array2<f64>,
243 shape_fn: &[f64],
244 shape_ds: &[f64],
245 shape_dt: &[f64],
246 weight: f64,
247 p_surf: Complex64,
248 v_surf: Complex64,
249 wavruim: f64,
250 n_nodes: usize,
251) -> Complex64 {
252 let mut crd_poi: Array1<f64> = Array1::zeros(3);
254 let mut dx_ds: Array1<f64> = Array1::zeros(3);
255 let mut dx_dt: Array1<f64> = Array1::zeros(3);
256
257 for n in 0..n_nodes {
258 for d in 0..3 {
259 crd_poi[d] += shape_fn[n] * elem_coords[[n, d]];
260 dx_ds[d] += shape_ds[n] * elem_coords[[n, d]];
261 dx_dt[d] += shape_dt[n] * elem_coords[[n, d]];
262 }
263 }
264
265 let normal: Array1<f64> = Array1::from_vec(vec![
267 dx_ds[1] * dx_dt[2] - dx_ds[2] * dx_dt[1],
268 dx_ds[2] * dx_dt[0] - dx_ds[0] * dx_dt[2],
269 dx_ds[0] * dx_dt[1] - dx_ds[1] * dx_dt[0],
270 ]);
271 let jacobian = normal.dot(&normal).sqrt();
272
273 if jacobian < 1e-15 {
274 return Complex64::new(0.0, 0.0);
275 }
276
277 let el_norm = &normal / jacobian;
278
279 let mut r_vec: Array1<f64> = Array1::zeros(3);
281 for d in 0..3 {
282 r_vec[d] = crd_poi[d] - x[d];
283 }
284 let r = r_vec.dot(&r_vec).sqrt();
285
286 if r < 1e-15 {
287 return Complex64::new(0.0, 0.0);
288 }
289
290 let vjacwe = jacobian * weight;
292
293 let kr = wavruim * r;
295 let re1 = 4.0 * PI * r;
296 let zgrfu = Complex64::new(kr.cos() / re1, kr.sin() / re1);
297
298 let z1 = Complex64::new(-1.0 / r, wavruim);
300 let zgikr = zgrfu * z1;
301
302 let drdn_y = r_vec.dot(&el_norm) / r;
304
305 let zdgrdn = zgikr * drdn_y;
307 let mut contrib = p_surf * zdgrdn * vjacwe;
308
309 if v_surf.norm() > 1e-15 {
311 contrib -= v_surf * zgrfu * vjacwe;
312 }
313
314 contrib
315}
316
317pub fn compute_total_field(
330 eval_points: &Array2<f64>,
331 elements: &[Element],
332 nodes: &Array2<f64>,
333 surface_pressure: &Array1<Complex64>,
334 surface_velocity: Option<&Array1<Complex64>>,
335 incident_field: &IncidentField,
336 physics: &PhysicsParams,
337) -> Vec<FieldPoint> {
338 let p_incident = incident_field.evaluate_pressure(eval_points, physics);
340
341 let p_scattered = compute_scattered_field(
343 eval_points,
344 elements,
345 nodes,
346 surface_pressure,
347 surface_velocity,
348 physics,
349 );
350
351 let n_eval = eval_points.nrows();
353 let mut results = Vec::with_capacity(n_eval);
354
355 for i in 0..n_eval {
356 let position = [
357 eval_points[[i, 0]],
358 eval_points[[i, 1]],
359 eval_points[[i, 2]],
360 ];
361 results.push(FieldPoint::new(position, p_incident[i], p_scattered[i]));
362 }
363
364 results
365}
366
367pub fn generate_sphere_eval_points(radius: f64, n_theta: usize, n_phi: usize) -> Array2<f64> {
377 let mut points = Vec::new();
378
379 for i in 0..n_theta {
380 let theta = PI * (i as f64 + 0.5) / n_theta as f64;
381 let sin_theta = theta.sin();
382 let cos_theta = theta.cos();
383
384 for j in 0..n_phi {
385 let phi = 2.0 * PI * j as f64 / n_phi as f64;
386
387 points.push(radius * sin_theta * phi.cos());
388 points.push(radius * sin_theta * phi.sin());
389 points.push(radius * cos_theta);
390 }
391 }
392
393 let n_points = n_theta * n_phi;
394 Array2::from_shape_vec((n_points, 3), points).unwrap()
395}
396
397pub fn generate_line_eval_points(start: [f64; 3], end: [f64; 3], n_points: usize) -> Array2<f64> {
407 let mut points = Vec::new();
408
409 for i in 0..n_points {
410 let t = i as f64 / (n_points - 1).max(1) as f64;
411 points.push(start[0] + t * (end[0] - start[0]));
412 points.push(start[1] + t * (end[1] - start[1]));
413 points.push(start[2] + t * (end[2] - start[2]));
414 }
415
416 Array2::from_shape_vec((n_points, 3), points).unwrap()
417}
418
419pub fn generate_plane_eval_points(
430 center: [f64; 3],
431 normal: [f64; 3],
432 extent: f64,
433 n_points: usize,
434) -> Array2<f64> {
435 let n = Array1::from_vec(vec![normal[0], normal[1], normal[2]]);
437 let n_norm = n.dot(&n).sqrt();
438 let n = &n / n_norm;
439
440 let arbitrary = if n[0].abs() < 0.9 {
442 Array1::from_vec(vec![1.0, 0.0, 0.0])
443 } else {
444 Array1::from_vec(vec![0.0, 1.0, 0.0])
445 };
446
447 let u = {
449 let cross = Array1::from_vec(vec![
450 n[1] * arbitrary[2] - n[2] * arbitrary[1],
451 n[2] * arbitrary[0] - n[0] * arbitrary[2],
452 n[0] * arbitrary[1] - n[1] * arbitrary[0],
453 ]);
454 let norm = cross.dot(&cross).sqrt();
455 cross / norm
456 };
457
458 let v = Array1::from_vec(vec![
460 n[1] * u[2] - n[2] * u[1],
461 n[2] * u[0] - n[0] * u[2],
462 n[0] * u[1] - n[1] * u[0],
463 ]);
464
465 let mut points = Vec::new();
466
467 for i in 0..n_points {
468 let s = -extent + 2.0 * extent * i as f64 / (n_points - 1).max(1) as f64;
469 for j in 0..n_points {
470 let t = -extent + 2.0 * extent * j as f64 / (n_points - 1).max(1) as f64;
471
472 points.push(center[0] + s * u[0] + t * v[0]);
473 points.push(center[1] + s * u[1] + t * v[1]);
474 points.push(center[2] + s * u[2] + t * v[2]);
475 }
476 }
477
478 Array2::from_shape_vec((n_points * n_points, 3), points).unwrap()
479}
480
481pub fn compute_rcs(
495 surface_pressure: &Array1<Complex64>,
496 elements: &[Element],
497 direction: [f64; 3],
498 physics: &PhysicsParams,
499) -> f64 {
500 let k = physics.wave_number;
501
502 let mut far_field = Complex64::new(0.0, 0.0);
506
507 let boundary_elements: Vec<_> = elements
508 .iter()
509 .filter(|e| !e.property.is_evaluation())
510 .collect();
511
512 let d = Array1::from_vec(vec![direction[0], direction[1], direction[2]]);
513
514 for (j, elem) in boundary_elements.iter().enumerate() {
515 let center = &elem.center;
516 let normal = &elem.normal;
517 let area = elem.area;
518
519 let phase = -k * center.dot(&d);
521 let exp_phase = Complex64::new(phase.cos(), phase.sin());
522
523 let n_dot_d = normal.dot(&d);
525
526 let ik = Complex64::new(0.0, k);
528 far_field += surface_pressure[j] * exp_phase * area * ik * n_dot_d;
529 }
530
531 4.0 * PI * far_field.norm_sqr()
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::core::types::ElementProperty;
540 use ndarray::array;
541
542 fn make_physics(k: f64) -> PhysicsParams {
543 let c = 343.0;
544 let freq = k * c / (2.0 * PI);
545 PhysicsParams::new(freq, c, 1.21, false)
546 }
547
548 #[test]
549 fn test_field_point_creation() {
550 let p_inc = Complex64::new(1.0, 0.0);
551 let p_scat = Complex64::new(0.5, 0.3);
552
553 let fp = FieldPoint::new([1.0, 0.0, 0.0], p_inc, p_scat);
554
555 assert!((fp.p_total - (p_inc + p_scat)).norm() < 1e-10);
556 assert!(fp.magnitude() > 0.0);
557 }
558
559 #[test]
560 fn test_generate_sphere_eval_points() {
561 let points = generate_sphere_eval_points(2.0, 10, 20);
562
563 assert_eq!(points.nrows(), 200);
564 assert_eq!(points.ncols(), 3);
565
566 for i in 0..points.nrows() {
568 let r =
569 (points[[i, 0]].powi(2) + points[[i, 1]].powi(2) + points[[i, 2]].powi(2)).sqrt();
570 assert!((r - 2.0).abs() < 1e-10);
571 }
572 }
573
574 #[test]
575 fn test_generate_line_eval_points() {
576 let points = generate_line_eval_points([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], 11);
577
578 assert_eq!(points.nrows(), 11);
579
580 assert!(points[[0, 0]].abs() < 1e-10);
582
583 assert!((points[[10, 0]] - 1.0).abs() < 1e-10);
585
586 let spacing = points[[1, 0]] - points[[0, 0]];
588 assert!((spacing - 0.1).abs() < 1e-10);
589 }
590
591 #[test]
592 fn test_generate_plane_eval_points() {
593 let points = generate_plane_eval_points([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], 1.0, 5);
594
595 assert_eq!(points.nrows(), 25);
596 assert_eq!(points.ncols(), 3);
597
598 for i in 0..points.nrows() {
600 assert!(points[[i, 2]].abs() < 1e-10);
601 }
602 }
603
604 #[test]
605 fn test_scattered_field_basic() {
606 use crate::core::types::BoundaryCondition;
608
609 let physics = make_physics(1.0);
610
611 let nodes =
613 Array2::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 1.0, 0.0])
614 .unwrap();
615
616 let elem = Element {
617 connectivity: vec![0, 1, 2],
618 element_type: crate::core::types::ElementType::Tri3,
619 property: ElementProperty::Surface,
620 normal: array![0.0, 0.0, 1.0],
621 node_normals: Array2::zeros((3, 3)),
622 center: array![0.5, 1.0 / 3.0, 0.0],
623 area: 0.5,
624 boundary_condition: BoundaryCondition::Velocity(vec![Complex64::new(0.0, 0.0)]),
625 group: 0,
626 dof_addresses: vec![0],
627 };
628
629 let elements = vec![elem];
630 let surface_pressure = Array1::from_vec(vec![Complex64::new(1.0, 0.0)]);
631
632 let eval_points = Array2::from_shape_vec((1, 3), vec![0.5, 0.5, 1.0]).unwrap();
634
635 let p_scat = compute_scattered_field(
636 &eval_points,
637 &elements,
638 &nodes,
639 &surface_pressure,
640 None,
641 &physics,
642 );
643
644 assert!(p_scat[0].norm() > 0.0);
646 }
647}