use crate::mesh::Point;
use num_complex::Complex64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PmlDirection {
X,
Y,
Z,
XY,
XZ,
YZ,
XYZ,
}
#[derive(Debug, Clone, Copy)]
pub enum PmlProfile {
Polynomial { power: usize },
Constant,
}
impl Default for PmlProfile {
fn default() -> Self {
PmlProfile::Polynomial { power: 2 }
}
}
#[derive(Debug, Clone)]
pub struct PmlRegion {
pub direction: PmlDirection,
pub start: [f64; 3],
pub end: [f64; 3],
pub sigma_max: f64,
pub profile: PmlProfile,
pub wavenumber: f64,
}
impl PmlRegion {
pub fn new(
direction: PmlDirection,
start: [f64; 3],
end: [f64; 3],
sigma_max: f64,
wavenumber: f64,
) -> Self {
Self {
direction,
start,
end,
sigma_max,
profile: PmlProfile::default(),
wavenumber,
}
}
pub fn x_positive(x_start: f64, thickness: f64, sigma_max: f64, wavenumber: f64) -> Self {
Self::new(
PmlDirection::X,
[x_start, f64::NEG_INFINITY, f64::NEG_INFINITY],
[x_start + thickness, f64::INFINITY, f64::INFINITY],
sigma_max,
wavenumber,
)
}
pub fn x_negative(x_end: f64, thickness: f64, sigma_max: f64, wavenumber: f64) -> Self {
Self::new(
PmlDirection::X,
[x_end - thickness, f64::NEG_INFINITY, f64::NEG_INFINITY],
[x_end, f64::INFINITY, f64::INFINITY],
sigma_max,
wavenumber,
)
}
pub fn y_positive(y_start: f64, thickness: f64, sigma_max: f64, wavenumber: f64) -> Self {
Self::new(
PmlDirection::Y,
[f64::NEG_INFINITY, y_start, f64::NEG_INFINITY],
[f64::INFINITY, y_start + thickness, f64::INFINITY],
sigma_max,
wavenumber,
)
}
pub fn y_negative(y_end: f64, thickness: f64, sigma_max: f64, wavenumber: f64) -> Self {
Self::new(
PmlDirection::Y,
[f64::NEG_INFINITY, y_end - thickness, f64::NEG_INFINITY],
[f64::INFINITY, y_end, f64::INFINITY],
sigma_max,
wavenumber,
)
}
pub fn contains(&self, point: &Point) -> bool {
let in_x = point.x >= self.start[0] && point.x <= self.end[0];
let in_y = point.y >= self.start[1] && point.y <= self.end[1];
let in_z = point.z >= self.start[2] && point.z <= self.end[2];
match self.direction {
PmlDirection::X => in_x,
PmlDirection::Y => in_y,
PmlDirection::Z => in_z,
PmlDirection::XY => in_x || in_y,
PmlDirection::XZ => in_x || in_z,
PmlDirection::YZ => in_y || in_z,
PmlDirection::XYZ => in_x || in_y || in_z,
}
}
pub fn stretching_x(&self, x: f64) -> Complex64 {
if !matches!(
self.direction,
PmlDirection::X | PmlDirection::XY | PmlDirection::XZ | PmlDirection::XYZ
) {
return Complex64::new(1.0, 0.0);
}
let sigma = self.sigma_at_position(x, 0);
Complex64::new(1.0, sigma / self.wavenumber)
}
pub fn stretching_y(&self, y: f64) -> Complex64 {
if !matches!(
self.direction,
PmlDirection::Y | PmlDirection::XY | PmlDirection::YZ | PmlDirection::XYZ
) {
return Complex64::new(1.0, 0.0);
}
let sigma = self.sigma_at_position(y, 1);
Complex64::new(1.0, sigma / self.wavenumber)
}
pub fn stretching_z(&self, z: f64) -> Complex64 {
if !matches!(
self.direction,
PmlDirection::Z | PmlDirection::XZ | PmlDirection::YZ | PmlDirection::XYZ
) {
return Complex64::new(1.0, 0.0);
}
let sigma = self.sigma_at_position(z, 2);
Complex64::new(1.0, sigma / self.wavenumber)
}
fn sigma_at_position(&self, pos: f64, dim: usize) -> f64 {
let start = self.start[dim];
let end = self.end[dim];
let thickness = (end - start).abs();
if thickness <= 0.0 {
return 0.0;
}
let dist = if end > start {
(pos - start).max(0.0).min(thickness)
} else {
(start - pos).max(0.0).min(thickness)
};
let normalized = dist / thickness;
match self.profile {
PmlProfile::Polynomial { power } => self.sigma_max * normalized.powi(power as i32),
PmlProfile::Constant => {
if normalized > 0.0 {
self.sigma_max
} else {
0.0
}
}
}
}
pub fn pml_factor(&self, point: &Point) -> Complex64 {
let sx = self.stretching_x(point.x);
let sy = self.stretching_y(point.y);
let sz = self.stretching_z(point.z);
sx * sy * sz
}
pub fn stiffness_coefficient_x(&self, point: &Point) -> Complex64 {
let sx = self.stretching_x(point.x);
let sy = self.stretching_y(point.y);
let sz = self.stretching_z(point.z);
sy * sz / sx
}
pub fn stiffness_coefficient_y(&self, point: &Point) -> Complex64 {
let sx = self.stretching_x(point.x);
let sy = self.stretching_y(point.y);
let sz = self.stretching_z(point.z);
sx * sz / sy
}
pub fn stiffness_coefficient_z(&self, point: &Point) -> Complex64 {
let sx = self.stretching_x(point.x);
let sy = self.stretching_y(point.y);
let sz = self.stretching_z(point.z);
sx * sy / sz
}
pub fn mass_coefficient(&self, point: &Point) -> Complex64 {
self.pml_factor(point)
}
}
pub fn optimal_sigma_max(
polynomial_power: usize,
thickness: f64,
wavenumber: f64,
target_reflection: f64,
) -> f64 {
let ln_r = -target_reflection.ln();
(polynomial_power + 1) as f64 * ln_r / (2.0 * thickness * wavenumber)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pml_region_creation() {
let pml = PmlRegion::x_positive(1.0, 0.5, 10.0, 1.0);
assert_eq!(pml.direction, PmlDirection::X);
assert_eq!(pml.start[0], 1.0);
assert_eq!(pml.end[0], 1.5);
}
#[test]
fn test_pml_contains() {
let pml = PmlRegion::x_positive(1.0, 0.5, 10.0, 1.0);
assert!(!pml.contains(&Point::new_2d(0.5, 0.0)));
assert!(pml.contains(&Point::new_2d(1.2, 0.0)));
assert!(pml.contains(&Point::new_2d(1.5, 0.0)));
assert!(!pml.contains(&Point::new_2d(1.6, 0.0)));
}
#[test]
fn test_pml_stretching() {
let pml = PmlRegion::x_positive(1.0, 0.5, 10.0, 1.0);
let s_inner = pml.stretching_x(1.0);
assert!((s_inner.re - 1.0).abs() < 1e-10);
assert!(s_inner.im.abs() < 1e-10);
let s_outer = pml.stretching_x(1.5);
assert!((s_outer.re - 1.0).abs() < 1e-10);
assert!(s_outer.im > 0.0);
}
#[test]
fn test_pml_coefficient_unity_outside() {
let pml = PmlRegion::x_positive(1.0, 0.5, 10.0, 1.0);
let point = Point::new_2d(0.5, 0.0);
let factor = pml.pml_factor(&point);
assert!((factor.re - 1.0).abs() < 1e-10);
assert!(factor.im.abs() < 1e-10);
}
#[test]
fn test_optimal_sigma() {
let sigma = optimal_sigma_max(2, 0.5, 1.0, 1e-6);
assert!(sigma > 0.0);
assert!(sigma < 100.0); }
}