use std::f64::consts::PI;
pub trait BEMKernel: Send + Sync {
fn g(&self, x: [f64; 2], y: [f64; 2]) -> f64;
fn dg_dn(&self, x: [f64; 2], y: [f64; 2], n: [f64; 2]) -> f64;
}
#[derive(Debug, Clone, Default)]
pub struct LaplaceKernel;
impl BEMKernel for LaplaceKernel {
fn g(&self, x: [f64; 2], y: [f64; 2]) -> f64 {
let r = ((x[0] - y[0]).powi(2) + (x[1] - y[1]).powi(2)).sqrt();
if r < 1e-15 {
return 0.0;
}
-1.0 / (2.0 * PI) * r.ln()
}
fn dg_dn(&self, x: [f64; 2], y: [f64; 2], n: [f64; 2]) -> f64 {
let dx = x[0] - y[0];
let dy_coord = x[1] - y[1];
let r2 = dx * dx + dy_coord * dy_coord;
if r2 < 1e-30 {
return 0.0;
}
(dx * n[0] + dy_coord * n[1]) / (2.0 * PI * r2)
}
}
#[derive(Debug, Clone)]
pub struct HelmholtzKernel {
pub k: f64,
}
impl HelmholtzKernel {
pub fn new(k: f64) -> Self {
Self { k }
}
fn bessel_j0(z: f64) -> f64 {
if z < 0.0 {
return Self::bessel_j0(-z);
}
if z <= 3.0 {
let t = z / 3.0;
let t2 = t * t;
1.0 - 2.2499997 * t2
+ 1.2656208 * t2 * t2
- 0.3163866 * t2 * t2 * t2
+ 0.0444479 * t2 * t2 * t2 * t2
- 0.0039444 * t2 * t2 * t2 * t2 * t2
+ 0.0002100 * t2 * t2 * t2 * t2 * t2 * t2
} else {
let t = 3.0 / z;
let f0 = 0.79788456
- 0.00000077 * t
- 0.00552740 * t * t
- 0.00009512 * t * t * t
+ 0.00137237 * t * t * t * t
- 0.00072805 * t * t * t * t * t
+ 0.00014476 * t * t * t * t * t * t;
let theta0 = z
- 0.78539816
- 0.04166397 * t
- 0.00003954 * t * t
+ 0.00262573 * t * t * t
- 0.00054125 * t * t * t * t
- 0.00029333 * t * t * t * t * t
+ 0.00013558 * t * t * t * t * t * t;
f0 / z.sqrt() * theta0.cos()
}
}
fn bessel_j1(z: f64) -> f64 {
if z <= 0.0 {
return 0.0;
}
if z <= 3.0 {
let t = z / 3.0;
let t2 = t * t;
z * (0.5
- 0.56249985 * t2
+ 0.21093573 * t2 * t2
- 0.03954289 * t2 * t2 * t2
+ 0.00443319 * t2 * t2 * t2 * t2
- 0.00031761 * t2 * t2 * t2 * t2 * t2
+ 0.00001109 * t2 * t2 * t2 * t2 * t2 * t2)
} else {
let t = 3.0 / z;
let f1 = 0.79788456
+ 0.00000156 * t
+ 0.01659667 * t * t
+ 0.00017105 * t * t * t
- 0.00249511 * t * t * t * t
+ 0.00113653 * t * t * t * t * t
- 0.00020033 * t * t * t * t * t * t;
let theta1 = z
- 2.35619449
+ 0.12499612 * t
+ 0.00005650 * t * t
- 0.00637879 * t * t * t
+ 0.00074348 * t * t * t * t
+ 0.00079824 * t * t * t * t * t
- 0.00029166 * t * t * t * t * t * t;
f1 / z.sqrt() * theta1.cos()
}
}
}
impl BEMKernel for HelmholtzKernel {
fn g(&self, x: [f64; 2], y: [f64; 2]) -> f64 {
let r = ((x[0] - y[0]).powi(2) + (x[1] - y[1]).powi(2)).sqrt();
if r < 1e-15 {
return 0.0;
}
let kr = self.k * r;
-Self::bessel_j0(kr) / (4.0 * PI)
}
fn dg_dn(&self, x: [f64; 2], y: [f64; 2], n: [f64; 2]) -> f64 {
let dx = x[0] - y[0];
let dy_coord = x[1] - y[1];
let r2 = dx * dx + dy_coord * dy_coord;
if r2 < 1e-30 {
return 0.0;
}
let r = r2.sqrt();
let kr = self.k * r;
let cos_theta = (dx * n[0] + dy_coord * n[1]) / r;
self.k * Self::bessel_j1(kr) * cos_theta / (4.0 * PI * r)
}
}
#[derive(Debug, Clone, Default)]
pub struct BiharmonicKernel;
impl BEMKernel for BiharmonicKernel {
fn g(&self, x: [f64; 2], y: [f64; 2]) -> f64 {
let r2 = (x[0] - y[0]).powi(2) + (x[1] - y[1]).powi(2);
if r2 < 1e-30 {
return 0.0;
}
let r = r2.sqrt();
r2 * r.ln() / (8.0 * PI)
}
fn dg_dn(&self, x: [f64; 2], y: [f64; 2], n: [f64; 2]) -> f64 {
let dx = x[0] - y[0];
let dy_coord = x[1] - y[1];
let r2 = dx * dx + dy_coord * dy_coord;
if r2 < 1e-30 {
return 0.0;
}
let r = r2.sqrt();
let dot = dx * n[0] + dy_coord * n[1];
-(2.0 * r.ln() + 1.0) * dot / (8.0 * PI)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_laplace_kernel_symmetry() {
let k = LaplaceKernel;
let x = [1.0, 0.5];
let y = [0.3, 0.8];
let gxy = k.g(x, y);
let gyx = k.g(y, x);
assert!((gxy - gyx).abs() < 1e-14, "Laplace G not symmetric");
}
#[test]
fn test_laplace_kernel_singular_point() {
let k = LaplaceKernel;
let x = [1.0, 1.0];
let val = k.g(x, x);
assert_eq!(val, 0.0);
}
#[test]
fn test_helmholtz_kernel_small_k() {
let k = HelmholtzKernel::new(0.1);
let x = [1.0, 0.0];
let y = [0.0, 0.0];
let val = k.g(x, y);
assert!(val.is_finite());
}
#[test]
fn test_biharmonic_kernel_zero() {
let k = BiharmonicKernel;
let x = [1.0, 1.0];
assert_eq!(k.g(x, x), 0.0);
}
}