1use ndarray::{Array1, Array2};
7use num_complex::Complex64;
8use std::f64::consts::PI;
9
10use crate::core::integration::gauss::{quad_quadrature, triangle_quadrature};
11use crate::core::integration::singular::generate_subelements;
12use crate::core::mesh::element::{cross_product, normalize};
13use crate::core::types::{ElementType, IntegrationResult, PhysicsParams};
14
15pub fn regular_integration(
34 source_point: &Array1<f64>,
35 source_normal: &Array1<f64>,
36 element_coords: &Array2<f64>,
37 element_type: ElementType,
38 element_area: f64,
39 physics: &PhysicsParams,
40 bc_values: Option<&[Complex64]>,
41 bc_type: i32,
42 compute_rhs: bool,
43) -> IntegrationResult {
44 let wavruim = physics.harmonic_factor * physics.wave_number;
45 let k2 = physics.wave_number * physics.wave_number;
46
47 let mut result = IntegrationResult::default();
48
49 let subelements = generate_subelements(source_point, element_coords, element_type, element_area);
51
52 let (_csi_nodes, _eta_nodes): (Vec<f64>, Vec<f64>) = match element_type {
56 ElementType::Tri3 => (vec![0.0, 1.0, 0.0], vec![0.0, 0.0, 1.0]),
57 ElementType::Quad4 => (vec![1.0, -1.0, -1.0, 1.0], vec![1.0, 1.0, -1.0, -1.0]),
58 };
59
60 for subel in &subelements {
62 let xice = subel.xi_center;
63 let etce = subel.eta_center;
64 let fase = subel.factor;
65 let fase2 = fase * fase;
66 let iforie = (fase.abs() - 1.0).abs() < 1e-10; let quad_points = get_quadrature_points(element_type, subel.gauss_order);
70
71 for (csi_gau, eta_gau, wei_gau) in quad_points {
72 let (xio, eto, weih2) = if iforie {
74 (csi_gau, eta_gau, wei_gau)
75 } else if let Some(tri_verts) = &subel.tri_vertices {
76 let l0 = 1.0 - csi_gau - eta_gau; let xio = tri_verts[0].0 * l0 + tri_verts[1].0 * csi_gau + tri_verts[2].0 * eta_gau;
82 let eto = tri_verts[0].1 * l0 + tri_verts[1].1 * csi_gau + tri_verts[2].1 * eta_gau;
83
84 let dx1 = tri_verts[1].0 - tri_verts[0].0;
87 let dy1 = tri_verts[1].1 - tri_verts[0].1;
88 let dx2 = tri_verts[2].0 - tri_verts[0].0;
89 let dy2 = tri_verts[2].1 - tri_verts[0].1;
90 let det = (dx1 * dy2 - dx2 * dy1).abs();
91 let weih2 = wei_gau * det;
94
95 (xio, eto, weih2)
96 } else {
97 (
99 xice + csi_gau * fase,
100 etce + eta_gau * fase,
101 wei_gau * fase2,
102 )
103 };
104
105 let (shape_fn, jacobian, el_norm, crd_poi) =
107 compute_parameters(element_coords, element_type, xio, eto);
108
109 let wga = weih2 * jacobian;
110
111 let mut diff_fsp = Array1::zeros(3);
113 for i in 0..3 {
114 diff_fsp[i] = crd_poi[i] - source_point[i];
115 }
116
117 let (unit_r, dis_fsp) = normalize(&diff_fsp);
118
119 if dis_fsp < 1e-15 {
120 continue;
121 }
122
123 let re1 = wavruim * dis_fsp;
125 let re2 = wga / (4.0 * PI * dis_fsp);
126 let zg = Complex64::new(re1.cos() * re2, re1.sin() * re2);
127
128 let z1 = Complex64::new(-1.0 / dis_fsp, wavruim);
130 let zhh_base = zg * z1;
131 let re1_h = unit_r.dot(&el_norm); let zhh = zhh_base * re1_h;
133
134 let re2_h = -unit_r.dot(source_normal); let zht = zhh_base * re2_h;
137
138 let rq = re1_h * re2_h;
140 let nx_dot_ny = source_normal.dot(&el_norm);
141 let dq = dis_fsp * dis_fsp;
142
143 let ze_factor = Complex64::new(
144 (3.0 / dq - k2) * rq + nx_dot_ny / dq,
145 -wavruim / dis_fsp * (3.0 * rq + nx_dot_ny),
146 );
147 let ze = zg * ze_factor;
148
149 result.g_integral += zg;
151 result.dg_dn_integral += zhh;
152 result.dg_dnx_integral += zht;
153 result.d2g_dnxdny_integral += ze;
154
155 if compute_rhs {
157 if let Some(bc) = bc_values {
158 let mut zbgao = Complex64::new(0.0, 0.0);
160 for (i, &n) in shape_fn.iter().enumerate() {
161 if i < bc.len() {
162 zbgao += bc[i] * n;
163 }
164 }
165
166 let gamma = physics.gamma();
167 let tau = physics.tau;
168 let beta = physics.burton_miller_beta();
169
170 if bc_type == 0 {
171 result.rhs_contribution += (zg * gamma * tau + zht * beta) * zbgao;
173 } else if bc_type == 1 {
174 result.rhs_contribution -= (zhh * gamma * tau + ze * beta) * zbgao;
176 }
177 }
178 }
179 }
180 }
181
182 result
183}
184
185fn get_quadrature_points(element_type: ElementType, order: usize) -> Vec<(f64, f64, f64)> {
187 match element_type {
188 ElementType::Tri3 => triangle_quadrature(order),
189 ElementType::Quad4 => quad_quadrature(order),
190 }
191}
192
193fn compute_parameters(
195 element_coords: &Array2<f64>,
196 element_type: ElementType,
197 s: f64,
198 t: f64,
199) -> (Vec<f64>, f64, Array1<f64>, Array1<f64>) {
200 let num_nodes = element_type.num_nodes();
201
202 let (shape_fn, shape_ds, shape_dt) = match element_type {
203 ElementType::Tri3 => {
204 let shape_fn = vec![1.0 - s - t, s, t];
209 let shape_ds = vec![-1.0, 1.0, 0.0];
210 let shape_dt = vec![-1.0, 0.0, 1.0];
211 (shape_fn, shape_ds, shape_dt)
212 }
213 ElementType::Quad4 => {
214 let s1 = 0.25 * (s + 1.0);
216 let s2 = 0.25 * (s - 1.0);
217 let t1 = t + 1.0;
218 let t2 = t - 1.0;
219
220 let shape_fn = vec![s1 * t1, -s2 * t1, s2 * t2, -s1 * t2];
221 let shape_ds = vec![
222 0.25 * (t + 1.0),
223 -0.25 * (t + 1.0),
224 0.25 * (t - 1.0),
225 -0.25 * (t - 1.0),
226 ];
227 let shape_dt = vec![
228 0.25 * (s + 1.0),
229 0.25 * (1.0 - s),
230 0.25 * (s - 1.0),
231 -0.25 * (s + 1.0),
232 ];
233 (shape_fn, shape_ds, shape_dt)
234 }
235 };
236
237 let mut crd_poi = Array1::zeros(3);
239 let mut dx_ds = Array1::zeros(3);
240 let mut dx_dt = Array1::zeros(3);
241
242 for i in 0..num_nodes {
243 for j in 0..3 {
244 crd_poi[j] += shape_fn[i] * element_coords[[i, j]];
245 dx_ds[j] += shape_ds[i] * element_coords[[i, j]];
246 dx_dt[j] += shape_dt[i] * element_coords[[i, j]];
247 }
248 }
249
250 let normal = cross_product(&dx_ds, &dx_dt);
252 let jacobian = normal.dot(&normal).sqrt();
253
254 let el_norm = if jacobian > 1e-15 {
255 normal / jacobian
256 } else {
257 Array1::zeros(3)
258 };
259
260 (shape_fn, jacobian, el_norm, crd_poi)
261}
262
263pub const QUASI_SINGULAR_THRESHOLD: f64 = 3.0;
266
267pub const HIGH_ACCURACY_THRESHOLD: f64 = 2.0;
269
270pub fn optimal_quadrature_order(distance: f64, element_size: f64) -> usize {
277 let ratio = distance / element_size;
278 if ratio < HIGH_ACCURACY_THRESHOLD {
279 4 } else if ratio < QUASI_SINGULAR_THRESHOLD {
281 3 } else {
283 2 }
285}
286
287pub fn quasi_singular_integration(
305 source_point: &Array1<f64>,
306 source_normal: &Array1<f64>,
307 element_coords: &Array2<f64>,
308 element_type: ElementType,
309 element_center: &Array1<f64>,
310 element_area: f64,
311 physics: &PhysicsParams,
312) -> IntegrationResult {
313 let mut dist_sq = 0.0;
315 for i in 0..3 {
316 let d = element_center[i] - source_point[i];
317 dist_sq += d * d;
318 }
319 let distance = dist_sq.sqrt();
320 let element_size = element_area.sqrt();
321 let ratio = distance / element_size;
322
323 if ratio < HIGH_ACCURACY_THRESHOLD {
325 regular_integration(
327 source_point,
328 source_normal,
329 element_coords,
330 element_type,
331 element_area,
332 physics,
333 None,
334 0,
335 false,
336 )
337 } else if ratio < QUASI_SINGULAR_THRESHOLD {
338 let order = optimal_quadrature_order(distance, element_size);
340 regular_integration_fixed_order(
341 source_point,
342 source_normal,
343 element_coords,
344 element_type,
345 physics,
346 order,
347 )
348 } else {
349 regular_integration_fixed_order(
351 source_point,
352 source_normal,
353 element_coords,
354 element_type,
355 physics,
356 2, )
358 }
359}
360
361pub fn regular_integration_fixed_order(
365 source_point: &Array1<f64>,
366 source_normal: &Array1<f64>,
367 element_coords: &Array2<f64>,
368 element_type: ElementType,
369 physics: &PhysicsParams,
370 gauss_order: usize,
371) -> IntegrationResult {
372 let wavruim = physics.harmonic_factor * physics.wave_number;
373 let k2 = physics.wave_number * physics.wave_number;
374
375 let mut result = IntegrationResult::default();
376
377 let quad_points = get_quadrature_points(element_type, gauss_order);
379
380 for (xi, eta, weight) in quad_points {
381 let (_, jacobian, el_norm, crd_poi) =
383 compute_parameters(element_coords, element_type, xi, eta);
384
385 let wga = weight * jacobian;
386
387 let mut diff_fsp = Array1::zeros(3);
389 for i in 0..3 {
390 diff_fsp[i] = crd_poi[i] - source_point[i];
391 }
392
393 let (unit_r, dis_fsp) = normalize(&diff_fsp);
394
395 if dis_fsp < 1e-15 {
396 continue;
397 }
398
399 let kr = wavruim * dis_fsp;
401 let g_scaled = wga / (4.0 * PI * dis_fsp);
402 let zg = Complex64::new(kr.cos() * g_scaled, kr.sin() * g_scaled);
403
404 let z1 = Complex64::new(-1.0 / dis_fsp, wavruim);
406 let zhh_base = zg * z1;
407 let r_dot_ny = unit_r.dot(&el_norm);
408 let r_dot_nx = -unit_r.dot(source_normal);
409 let zhh = zhh_base * r_dot_ny;
410 let zht = zhh_base * r_dot_nx;
411
412 let rq = r_dot_ny * r_dot_nx;
414 let nx_dot_ny = source_normal.dot(&el_norm);
415 let dq = dis_fsp * dis_fsp;
416
417 let ze_factor = Complex64::new(
418 (3.0 / dq - k2) * rq + nx_dot_ny / dq,
419 -wavruim / dis_fsp * (3.0 * rq + nx_dot_ny),
420 );
421 let ze = zg * ze_factor;
422
423 result.g_integral += zg;
425 result.dg_dn_integral += zhh;
426 result.dg_dnx_integral += zht;
427 result.d2g_dnxdny_integral += ze;
428 }
429
430 result
431}
432
433pub fn integrate_g_only(
437 source_point: &Array1<f64>,
438 element_coords: &Array2<f64>,
439 element_type: ElementType,
440 physics: &PhysicsParams,
441 gauss_order: usize,
442) -> Complex64 {
443 let wavruim = physics.harmonic_factor * physics.wave_number;
444 let mut result = Complex64::new(0.0, 0.0);
445
446 let quad_points = get_quadrature_points(element_type, gauss_order);
447
448 for (xi, eta, weight) in quad_points {
449 let (_, jacobian, _, crd_poi) =
450 compute_parameters(element_coords, element_type, xi, eta);
451
452 let wga = weight * jacobian;
453
454 let mut diff = Array1::zeros(3);
455 for i in 0..3 {
456 diff[i] = crd_poi[i] - source_point[i];
457 }
458 let r = diff.dot(&diff).sqrt();
459
460 if r < 1e-15 {
461 continue;
462 }
463
464 let kr = wavruim * r;
465 let g_scaled = wga / (4.0 * PI * r);
466 result += Complex64::new(kr.cos() * g_scaled, kr.sin() * g_scaled);
467 }
468
469 result
470}
471
472pub fn integrate_h_only(
476 source_point: &Array1<f64>,
477 element_coords: &Array2<f64>,
478 element_type: ElementType,
479 physics: &PhysicsParams,
480 gauss_order: usize,
481) -> Complex64 {
482 let wavruim = physics.harmonic_factor * physics.wave_number;
483 let mut result = Complex64::new(0.0, 0.0);
484
485 let quad_points = get_quadrature_points(element_type, gauss_order);
486
487 for (xi, eta, weight) in quad_points {
488 let (_, jacobian, el_norm, crd_poi) =
489 compute_parameters(element_coords, element_type, xi, eta);
490
491 let wga = weight * jacobian;
492
493 let mut diff = Array1::zeros(3);
494 for i in 0..3 {
495 diff[i] = crd_poi[i] - source_point[i];
496 }
497 let r = diff.dot(&diff).sqrt();
498
499 if r < 1e-15 {
500 continue;
501 }
502
503 let kr = wavruim * r;
504 let g_scaled = wga / (4.0 * PI * r);
505 let zg = Complex64::new(kr.cos() * g_scaled, kr.sin() * g_scaled);
506
507 let z1 = Complex64::new(-1.0 / r, wavruim);
508 let r_dot_n: f64 = diff.iter().zip(el_norm.iter()).map(|(d, n)| d * n).sum::<f64>() / r;
509
510 result += zg * z1 * r_dot_n;
511 }
512
513 result
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use ndarray::{array, Array2};
520
521 fn make_test_triangle() -> Array2<f64> {
522 Array2::from_shape_vec((3, 3), vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]).unwrap()
523 }
524
525 fn make_test_quad() -> Array2<f64> {
526 Array2::from_shape_vec(
527 (4, 3),
528 vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0],
529 )
530 .unwrap()
531 }
532
533 #[test]
534 fn test_regular_integration_far_field() {
535 let coords = make_test_triangle();
536 let source = array![10.0, 0.0, 0.0]; let normal = array![0.0, 0.0, 1.0];
538
539 let physics = PhysicsParams::new(1000.0, 343.0, 1.21, false);
540
541 let result = regular_integration(
542 &source,
543 &normal,
544 &coords,
545 ElementType::Tri3,
546 0.5,
547 &physics,
548 None,
549 0,
550 false,
551 );
552
553 assert!(result.g_integral.norm().is_finite());
555 assert!(result.g_integral.norm() < 0.1);
556 }
557
558 #[test]
559 fn test_regular_integration_fixed_order() {
560 let coords = make_test_triangle();
561 let source = array![5.0, 5.0, 0.0];
562 let normal = array![0.0, 0.0, 1.0];
563
564 let physics = PhysicsParams::new(1000.0, 343.0, 1.21, false);
565
566 let result = regular_integration_fixed_order(
567 &source,
568 &normal,
569 &coords,
570 ElementType::Tri3,
571 &physics,
572 4,
573 );
574
575 assert!(result.g_integral.norm().is_finite());
576 }
577
578 #[test]
579 fn test_integrate_g_only() {
580 let coords = make_test_triangle();
581 let source = array![2.0, 2.0, 1.0];
582
583 let physics = PhysicsParams::new(1000.0, 343.0, 1.21, false);
584
585 let g = integrate_g_only(&source, &coords, ElementType::Tri3, &physics, 4);
586
587 assert!(g.norm().is_finite());
588 assert!(g.norm() > 0.0);
589 }
590
591 #[test]
592 fn test_quad_element_integration() {
593 let coords = make_test_quad();
594 let source = array![5.0, 0.5, 0.0];
595 let normal = array![0.0, 0.0, 1.0];
596
597 let physics = PhysicsParams::new(1000.0, 343.0, 1.21, false);
598
599 let result = regular_integration_fixed_order(
600 &source,
601 &normal,
602 &coords,
603 ElementType::Quad4,
604 &physics,
605 4,
606 );
607
608 assert!(result.g_integral.norm().is_finite());
609 }
610
611 #[test]
612 fn test_integration_symmetry() {
613 let coords = make_test_triangle();
615 let source1 = array![0.5, 0.5, 1.0];
616 let normal1 = array![0.0, 0.0, 1.0];
617 let source2 = array![0.5, 0.5, -1.0];
618 let normal2 = array![0.0, 0.0, -1.0];
619
620 let physics = PhysicsParams::new(1000.0, 343.0, 1.21, false);
621
622 let result1 = regular_integration_fixed_order(
623 &source1,
624 &normal1,
625 &coords,
626 ElementType::Tri3,
627 &physics,
628 4,
629 );
630
631 let result2 = regular_integration_fixed_order(
632 &source2,
633 &normal2,
634 &coords,
635 ElementType::Tri3,
636 &physics,
637 4,
638 );
639
640 assert!((result1.g_integral.norm() - result2.g_integral.norm()).abs() < 1e-10);
642 }
643
644 #[test]
645 fn test_compute_parameters_triangle() {
646 let coords = make_test_triangle();
647
648 let (shape, jac, normal, pos) = compute_parameters(&coords, ElementType::Tri3, 0.5, 0.25);
649
650 let sum: f64 = shape.iter().sum();
652 assert!((sum - 1.0).abs() < 1e-10);
653
654 assert!((jac - 1.0).abs() < 1e-10);
656
657 assert!(normal[2].abs() > 0.99);
659
660 assert!((pos[0] - 0.5).abs() < 1e-10);
662 assert!((pos[1] - 0.25).abs() < 1e-10);
663 }
664
665 #[test]
666 fn test_compute_parameters_quad() {
667 let coords = make_test_quad();
668
669 let (shape, jac, normal, _pos) = compute_parameters(&coords, ElementType::Quad4, 0.0, 0.0);
670
671 let sum: f64 = shape.iter().sum();
673 assert!((sum - 1.0).abs() < 1e-10);
674
675 assert!(normal[2].abs() > 0.99);
677 }
678}