use crate::units::conversion::EPSILON_0;
#[derive(Debug, Clone)]
pub struct EUpdateCoeffs {
pub ca: Vec<f64>,
pub cb: Vec<f64>,
pub is_pec: Vec<bool>,
pub n: usize,
}
impl EUpdateCoeffs {
pub fn new_1d(eps_r: &[f64], sigma_e: &[f64], dt: f64, dz: f64) -> Self {
let n = eps_r.len();
assert_eq!(sigma_e.len(), n);
let mut ca = vec![1.0; n];
let mut cb = vec![0.0; n];
let is_pec = vec![false; n];
for i in 0..n {
let eps = eps_r[i] * EPSILON_0;
let sig = sigma_e[i];
let denom = eps + sig * dt / 2.0;
ca[i] = (eps - sig * dt / 2.0) / denom;
cb[i] = dt / (dz * denom);
}
Self { ca, cb, is_pec, n }
}
pub fn lossless_1d(eps_r: &[f64], dt: f64, dz: f64) -> Self {
let sigma = vec![0.0; eps_r.len()];
Self::new_1d(eps_r, &sigma, dt, dz)
}
pub fn set_pec(&mut self, i_start: usize, i_end: usize) {
for i in i_start..i_end.min(self.n) {
self.is_pec[i] = true;
self.ca[i] = 0.0;
self.cb[i] = 0.0;
}
}
pub fn apply_1d_te(&self, ex: &mut [f64], hy: &[f64]) {
let n = self.n;
for i in 1..n - 1 {
if self.is_pec[i] {
ex[i] = 0.0;
} else {
ex[i] = self.ca[i] * ex[i] + self.cb[i] * (hy[i] - hy[i - 1]);
}
}
}
pub fn apply_1d_with_pml(
&self,
ex: &mut [f64],
hy: &[f64],
dz: f64,
kappa_e: &[f64],
psi_ex: &[f64],
) {
let n = self.n;
for i in 1..n - 1 {
if self.is_pec[i] {
ex[i] = 0.0;
} else {
let dhy = (hy[i] - hy[i - 1]) / dz;
ex[i] = self.ca[i] * ex[i] + self.cb[i] * dz * (dhy / kappa_e[i] + psi_ex[i]);
}
}
}
}
#[derive(Debug, Clone)]
pub struct EUpdateCoeffs2d {
pub nx: usize,
pub ny: usize,
pub ca_ex: Vec<f64>,
pub cb_ex: Vec<f64>,
pub ca_ey: Vec<f64>,
pub cb_ey: Vec<f64>,
pub is_pec: Vec<bool>,
}
impl EUpdateCoeffs2d {
pub fn new(nx: usize, ny: usize, eps_r: &[f64], sigma: &[f64], dt: f64, dx: f64) -> Self {
let n = nx * ny;
assert_eq!(eps_r.len(), n);
assert_eq!(sigma.len(), n);
let mut ca = vec![1.0; n];
let mut cb = vec![0.0; n];
for i in 0..n {
let eps = eps_r[i] * EPSILON_0;
let sig = sigma[i];
let denom = eps + sig * dt / 2.0;
ca[i] = (eps - sig * dt / 2.0) / denom;
cb[i] = dt / (dx * denom);
}
Self {
nx,
ny,
ca_ex: ca.clone(),
cb_ex: cb.clone(),
ca_ey: ca,
cb_ey: cb,
is_pec: vec![false; n],
}
}
pub fn set_pec_box(&mut self, ix0: usize, ix1: usize, iy0: usize, iy1: usize) {
let ny = self.ny;
for i in ix0..ix1.min(self.nx) {
for j in iy0..iy1.min(ny) {
let idx = i * ny + j;
self.is_pec[idx] = true;
self.ca_ex[idx] = 0.0;
self.cb_ex[idx] = 0.0;
self.ca_ey[idx] = 0.0;
self.cb_ey[idx] = 0.0;
}
}
}
pub fn apply_ex(&self, ex: &mut [f64], hz: &[f64], dy: f64) {
let ny = self.ny;
for i in 0..self.nx {
for j in 1..ny {
let idx = i * ny + j;
if self.is_pec[idx] {
ex[idx] = 0.0;
} else {
let curl = (hz[idx] - hz[idx - 1]) / dy;
ex[idx] = self.ca_ex[idx] * ex[idx] + self.cb_ex[idx] * dy * curl;
}
}
}
}
pub fn apply_ey(&self, ey: &mut [f64], hz: &[f64], dx: f64) {
let ny = self.ny;
for i in 1..self.nx {
for j in 0..ny {
let idx = i * ny + j;
if self.is_pec[idx] {
ey[idx] = 0.0;
} else {
let curl = (hz[idx] - hz[(i - 1) * ny + j]) / dx;
ey[idx] = self.ca_ey[idx] * ey[idx] - self.cb_ey[idx] * dx * curl;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn ca_lossless_is_one() {
let eps_r = vec![1.0; 10];
let c = EUpdateCoeffs::lossless_1d(&eps_r, 1e-16, 10e-9);
for i in 0..10 {
assert_relative_eq!(c.ca[i], 1.0, epsilon = 1e-12);
}
}
#[test]
fn cb_lossless_positive() {
let eps_r = vec![2.25; 10]; let dt = 1e-16;
let dz = 10e-9;
let c = EUpdateCoeffs::lossless_1d(&eps_r, dt, dz);
for i in 0..10 {
assert!(c.cb[i] > 0.0, "cb[{i}] = {:.4e}", c.cb[i]);
}
}
#[test]
fn pec_zeroes_ca_cb() {
let eps_r = vec![1.0; 10];
let mut c = EUpdateCoeffs::lossless_1d(&eps_r, 1e-16, 10e-9);
c.set_pec(3, 6);
for i in 3..6 {
assert_eq!(c.ca[i], 0.0);
assert_eq!(c.cb[i], 0.0);
assert!(c.is_pec[i]);
}
assert!(!c.is_pec[2]);
assert!(!c.is_pec[6]);
}
#[test]
fn apply_1d_te_updates_interior() {
let eps_r = vec![1.0; 10];
let c = EUpdateCoeffs::lossless_1d(&eps_r, 1e-16, 10e-9);
let mut ex = vec![0.0; 10];
let hy = vec![1.0; 10];
c.apply_1d_te(&mut ex, &hy);
for (i, val) in ex.iter().enumerate().skip(1).take(8) {
assert_eq!(*val, 0.0, "Uniform H → zero curl → no E update at {i}");
}
}
#[test]
fn apply_1d_te_nonuniform_h() {
let eps_r = vec![1.0; 10];
let c = EUpdateCoeffs::lossless_1d(&eps_r, 1e-16, 10e-9);
let mut ex = vec![0.0; 10];
let mut hy = vec![0.0; 10];
hy[4] = 1.0; c.apply_1d_te(&mut ex, &hy);
assert!(ex[5].is_finite());
}
#[test]
fn e_update_2d_zeros_for_uniform_hz() {
let n = 4 * 4;
let eps_r = vec![1.0; n];
let sigma = vec![0.0; n];
let c = EUpdateCoeffs2d::new(4, 4, &eps_r, &sigma, 1e-16, 10e-9);
let mut ex = vec![0.0; n];
let hz = vec![1.0; n]; c.apply_ex(&mut ex, &hz, 10e-9);
assert!(ex.iter().all(|&v| v == 0.0));
}
#[test]
fn lossy_ca_less_than_one() {
let eps_r = vec![1.0; 5];
let sigma_e = vec![1e3; 5]; let c = EUpdateCoeffs::new_1d(&eps_r, &sigma_e, 1e-16, 10e-9);
for i in 0..5 {
assert!(c.ca[i] < 1.0, "Lossy Ca should be < 1");
}
}
}