use super::Engine;
use super::excite::Amplitude;
use super::fields::Fields;
use super::operator::{Hooks, Operator, StepContext};
use crate::axis::Axis;
use crate::constants::{C0, EPS0, MU0};
use crate::post::Complex64;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WavePart {
Real,
Imag,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PeriodicPlaneWaveSpec {
pub axis: Axis,
pub start: usize,
pub stop: usize,
pub forward: bool,
pub k_t: [f64; 3],
pub polarization: [f64; 3],
pub eps_r: f64,
pub mu_r: f64,
}
impl PeriodicPlaneWaveSpec {
pub fn new(
axis: Axis,
start: usize,
stop: usize,
forward: bool,
k_t: [f64; 3],
polarization: [f64; 3],
) -> Self {
Self {
axis,
start,
stop,
forward,
k_t,
polarization,
eps_r: 1.0,
mu_r: 1.0,
}
}
#[must_use]
pub fn with_background(mut self, eps_r: f64, mu_r: f64) -> Self {
self.eps_r = eps_r;
self.mu_r = mu_r;
self
}
pub fn angle(&self, f: f64) -> Option<f64> {
let kt = norm(self.k_t);
let k = 2.0 * core::f64::consts::PI * f * (self.eps_r * self.mu_r).sqrt() / C0;
(kt <= k).then(|| (kt / k).asin())
}
}
fn norm(v: [f64; 3]) -> f64 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
#[derive(Debug, Clone, Copy)]
struct Correction {
axis: Axis,
node: [usize; 3],
comp: usize,
m: usize,
wr: f64,
wi: f64,
}
const AUX_PML: usize = 64;
const AUX_PML_M: f64 = 3.0;
#[derive(Debug, Clone)]
struct Aux {
n: usize,
kt: [f64; 3],
d: Vec<f64>,
dd: Vec<f64>,
cb: f64,
db: f64,
b_node: Vec<f64>,
b_dual: Vec<f64>,
e: [Vec<Complex64>; 3],
h: [Vec<Complex64>; 3],
psi_e: [Vec<Complex64>; 3],
psi_h: [Vec<Complex64>; 3],
src: usize,
end: usize,
}
fn mj(k: f64, x: Complex64) -> Complex64 {
Complex64::new(k * x.im, -k * x.re)
}
fn stretched(diff: Complex64, b: f64, psi: &mut Complex64) -> Complex64 {
if b == 1.0 {
return diff;
}
*psi = *psi * b + diff * (b - 1.0);
diff + *psi
}
impl Aux {
fn update_h(&mut self) {
let n = self.n;
let nodes = self.d.len() + 1;
for a in 0..3 {
let (b, c) = ((a + 1) % 3, (a + 2) % 3);
if a == n {
for j in 0..nodes {
let curl = mj(self.kt[b], self.e[c][j]) - mj(self.kt[c], self.e[b][j]);
self.h[a][j] -= curl * self.db;
}
} else {
let (t, s) = if b == n { (c, 1.0) } else { (b, -1.0) };
let other = if b == n { c } else { b };
for j in 0..nodes - 1 {
let diff = (self.e[t][j + 1] - self.e[t][j]) * (1.0 / self.d[j]);
let dz = stretched(diff, self.b_dual[j], &mut self.psi_h[a][j]);
let tr = mj(self.kt[other], self.e[n][j]);
let curl = dz * s - tr * s;
self.h[a][j] -= curl * self.db;
}
}
}
}
fn update_e(&mut self) {
let n = self.n;
let nodes = self.d.len() + 1;
for a in 0..3 {
let (b, c) = ((a + 1) % 3, (a + 2) % 3);
if a == n {
for j in 0..nodes - 1 {
let curl = mj(self.kt[b], self.h[c][j]) - mj(self.kt[c], self.h[b][j]);
self.e[a][j] += curl * self.cb;
}
} else {
let (t, s) = if b == n { (c, 1.0) } else { (b, -1.0) };
let other = if b == n { c } else { b };
for j in 1..nodes - 1 {
if j == self.src || j == self.end {
continue;
}
let diff = (self.h[t][j] - self.h[t][j - 1]) * (1.0 / self.dd[j]);
let dz = stretched(diff, self.b_node[j], &mut self.psi_e[a][j]);
let tr = mj(self.kt[other], self.h[n][j]);
let curl = dz * s - tr * s;
self.e[a][j] += curl * self.cb;
}
}
}
}
}
pub struct PeriodicPlaneWave {
spec: PeriodicPlaneWaveSpec,
part: WavePart,
amplitude: Amplitude,
aux: Aux,
entry: usize,
e_corr: Vec<Correction>,
h_corr: Vec<Correction>,
incident: Vec<[Complex64; 3]>,
}
impl core::fmt::Debug for PeriodicPlaneWave {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PeriodicPlaneWave")
.field("spec", &self.spec)
.field("part", &self.part)
.field("e_corrections", &self.e_corr.len())
.field("h_corrections", &self.h_corr.len())
.finish_non_exhaustive()
}
}
fn discrete_k(lines: &[f64], k: f64) -> f64 {
if k == 0.0 || lines.len() < 2 {
return k;
}
let d = (lines[lines.len() - 1] - lines[0]) / (lines.len() - 1) as f64;
2.0 * (0.5 * k * d).sin() / d
}
impl PeriodicPlaneWave {
pub fn new(
engine: &Engine,
spec: &PeriodicPlaneWaveSpec,
part: WavePart,
amplitude: impl Fn(f64) -> f64 + Send + 'static,
) -> Result<Self> {
let grid = engine.grid();
let nn = grid.nodes();
let ax = spec.axis;
let n = ax.index();
let (lo, hi) = (spec.start, spec.stop);
if lo >= hi || hi >= nn[n] {
return Err(Error::Invalid(format!(
"periodic plane wave: planes {lo}..={hi} must have start < stop within the \
{} nodes along {ax}",
nn[n]
)));
}
let entry_3d = if spec.forward { lo } else { hi };
if entry_3d == 0 || entry_3d == nn[n] - 1 {
return Err(Error::Invalid(format!(
"periodic plane wave: the entry plane {entry_3d} lies on the domain boundary"
)));
}
if spec.k_t.iter().any(|k| !k.is_finite()) || spec.k_t[n] != 0.0 {
return Err(Error::Invalid(format!(
"periodic plane wave: transverse wavevector {:?} must be finite with no \
component along {ax}",
spec.k_t
)));
}
let p = spec.polarization;
let pl = norm(p);
if !(pl.is_finite() && pl > 0.0) || p[n] != 0.0 {
return Err(Error::Invalid(format!(
"periodic plane wave: polarisation {p:?} must be non-zero, finite and \
tangential to the planes"
)));
}
let (er, mr) = (spec.eps_r, spec.mu_r);
if !(er.is_finite() && er > 0.0 && mr.is_finite() && mr > 0.0) {
return Err(Error::Invalid(format!(
"periodic plane wave: invalid background eps_r = {er}, mu_r = {mr}"
)));
}
let dt = engine.dt();
let lines = grid.lines(ax);
let m0 = if spec.forward {
lo.min(2)
} else {
(nn[n] - 1 - hi).min(2)
};
let (first, last) = if spec.forward {
(lo - m0, (hi + 2).min(nn[n] - 1))
} else {
(lo.saturating_sub(2), hi + m0)
};
let mut z: Vec<f64> = lines[first..=last].to_vec();
let off = if spec.forward {
let d = z[z.len() - 1] - z[z.len() - 2];
for _ in 0..AUX_PML + 1 {
let x = z[z.len() - 1] + d;
z.push(x);
}
first as isize
} else {
let d = z[1] - z[0];
for _ in 0..AUX_PML + 1 {
z.insert(0, z[0] - d);
}
first as isize - (AUX_PML + 1) as isize
};
let nodes = z.len();
let d: Vec<f64> = z.windows(2).map(|w| w[1] - w[0]).collect();
let dd: Vec<f64> = (0..nodes)
.map(|j| {
let a = if j > 0 { d[j - 1] } else { 0.0 };
let b = if j + 1 < nodes { d[j] } else { 0.0 };
0.5 * (a + b)
})
.collect();
let (src, end) = if spec.forward {
(0, nodes - 1)
} else {
(nodes - 1, 0)
};
let (eps, mu) = (EPS0 * er, MU0 * mr);
let eta = (mu / eps).sqrt();
let dl = if spec.forward { d[d.len() - 1] } else { d[0] };
let smax = 0.8 * (AUX_PML_M + 1.0) / (eta * dl);
let depth = |pos: f64| -> f64 {
let x = if spec.forward {
pos - (nodes - 1 - AUX_PML - 1) as f64
} else {
(AUX_PML + 1) as f64 - pos
};
x.clamp(0.0, AUX_PML as f64)
};
let bfac = |pos: f64| {
let s = smax * (depth(pos) / AUX_PML as f64).powf(AUX_PML_M);
(-s * dt / eps).exp()
};
let b_node = (0..nodes).map(|j| bfac(j as f64)).collect();
let b_dual = (0..nodes - 1).map(|j| bfac(j as f64 + 0.5)).collect();
let kt = Axis::ALL.map(|a| {
if a == ax {
0.0
} else {
discrete_k(grid.lines(a), spec.k_t[a.index()])
}
});
let alloc = |at_node: bool| vec![Complex64::ZERO; if at_node { nodes } else { nodes - 1 }];
let aux = Aux {
n,
kt,
d,
dd,
cb: dt / eps,
db: dt / mu,
b_node,
b_dual,
e: [0, 1, 2].map(|a| alloc(a != n)),
h: [0, 1, 2].map(|a| alloc(a == n)),
psi_e: [0, 1, 2].map(|a| alloc(a != n)),
psi_h: [0, 1, 2].map(|a| alloc(a == n)),
src,
end,
};
let aux_of = |node3: usize| -> usize { (node3 as isize - off) as usize };
let entry = aux_of(entry_3d);
let r0 = Axis::ALL.map(|a| grid.lines(a)[0]);
let phase = |r: [f64; 3]| (0..3).map(|a| spec.k_t[a] * (r[a] - r0[a])).sum::<f64>();
let weights = |phi: f64| {
let (s, c) = phi.sin_cos();
match part {
WavePart::Real => (c, s),
WavePart::Imag => (-s, c),
}
};
let e_pos = |a: Axis, q: [usize; 3]| {
let (b, c) = a.others();
let mut r = [0.0; 3];
r[a.index()] = grid.dual_line(a, q[a.index()]);
r[b.index()] = grid.lines(b)[q[b.index()]];
r[c.index()] = grid.lines(c)[q[c.index()]];
r
};
let h_pos = |dax: Axis, q: [usize; 3]| {
let (e, f) = dax.others();
let mut r = [0.0; 3];
r[dax.index()] = grid.lines(dax)[q[dax.index()]];
r[e.index()] = grid.dual_line(e, q[e.index()]);
r[f.index()] = grid.dual_line(f, q[f.index()]);
r
};
let tf_h = |dax: Axis, q: [usize; 3]| {
if dax == ax {
(lo..=hi).contains(&q[n])
} else {
(lo..hi).contains(&q[n])
}
};
let h_valid = |dax: Axis, q: [usize; 3]| {
let (e, f) = dax.others();
(0..3).all(|a| q[a] < nn[a])
&& q[e.index()] + 1 < nn[e.index()]
&& q[f.index()] + 1 < nn[f.index()]
};
let co = engine.coefficients();
let (mut e_corr, mut h_corr) = (Vec::new(), Vec::new());
for plane in [lo, hi] {
for a in Axis::ALL.into_iter().filter(|&a| a != ax) {
let (b, c) = a.others();
let t = if b == ax { c } else { b };
let (ai, ti) = (a.index(), t.index());
for pa in 0..nn[ai] - 1 {
for pt in 0..nn[ti] {
let mut pnode = [0; 3];
(pnode[ai], pnode[ti], pnode[n]) = (pa, pt, plane);
let cbv = co.cb(a)[grid.index(pnode)] as f64;
let minus = |s: usize| {
let mut q = pnode;
q[s] = q[s].wrapping_sub(1);
q
};
let terms = [
(c, pnode, 1.0),
(c, minus(b.index()), -1.0),
(b, pnode, -1.0),
(b, minus(c.index()), 1.0),
];
for (dax, q, s) in terms {
if !h_valid(dax, q) || tf_h(dax, q) {
continue;
}
let di = dax.index();
debug_assert_ne!(dax, ax);
if cbv != 0.0 {
let (wr, wi) = weights(phase(h_pos(dax, q)));
let coef = cbv * s * grid.dual_delta(dax, q[di]);
e_corr.push(Correction {
axis: a,
node: pnode,
comp: di,
m: aux_of(q[n]),
wr: coef * wr,
wi: coef * wi,
});
}
let dbv = co.db(dax)[grid.index(q)] as f64;
if dbv != 0.0 {
let (wr, wi) = weights(phase(e_pos(a, pnode)));
let coef = dbv * s * grid.delta(a, pa);
h_corr.push(Correction {
axis: dax,
node: q,
comp: ai,
m: aux_of(plane),
wr: coef * wr,
wi: coef * wi,
});
}
}
}
}
}
}
drop(co);
Ok(Self {
spec: spec.clone(),
part,
amplitude: Box::new(amplitude),
aux,
entry,
e_corr,
h_corr,
incident: Vec::new(),
})
}
pub fn spec(&self) -> &PeriodicPlaneWaveSpec {
&self.spec
}
pub fn part(&self) -> WavePart {
self.part
}
pub fn corrections(&self) -> (usize, usize) {
(self.e_corr.len(), self.h_corr.len())
}
pub fn discrete_wavevector(&self) -> [f64; 3] {
self.aux.kt
}
pub fn incident(&self) -> &[[Complex64; 3]] {
&self.incident
}
}
impl Operator for PeriodicPlaneWave {
fn hooks(&self) -> Hooks {
Hooks::POST_H | Hooks::POST_E
}
fn post_h(&mut self, fields: &mut Fields<'_>, _ctx: &StepContext<'_>) {
for c in &self.h_corr {
let v = self.aux.e[c.comp][c.m];
*fields.i.get_mut(c.axis, c.node) += (c.wr * v.re + c.wi * v.im) as f32;
}
self.aux.update_h();
}
fn post_e(&mut self, fields: &mut Fields<'_>, ctx: &StepContext<'_>) {
for c in &self.e_corr {
let v = self.aux.h[c.comp][c.m];
*fields.u.get_mut(c.axis, c.node) += (c.wr * v.re + c.wi * v.im) as f32;
}
self.aux.update_e();
let a = (self.amplitude)(ctx.e_time());
let n = self.aux.n;
for t in (0..3).filter(|&t| t != n) {
self.aux.e[t][self.aux.src] = Complex64::new(self.spec.polarization[t] * a, 0.0);
self.aux.e[t][self.aux.end] = Complex64::ZERO;
}
let j = self.entry;
let mut inc = [Complex64::ZERO; 3];
for (t, v) in inc.iter_mut().enumerate() {
*v = if t == n {
(self.aux.e[n][j - 1] + self.aux.e[n][j]) * 0.5
} else {
self.aux.e[t][j]
};
}
self.incident.push(inc);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn discrete_wavenumber() {
let l: Vec<f64> = (0..11).map(|i| i as f64 * 1e-3).collect();
let k = 300.0;
let kt = discrete_k(&l, k);
assert!((kt - 2.0 * (0.5 * k * 1e-3).sin() / 1e-3).abs() < 1e-12);
assert!(kt < k);
assert_eq!(discrete_k(&l, 0.0), 0.0);
}
#[test]
fn angle_and_cutoff() {
let s = PeriodicPlaneWaveSpec::new(Axis::Z, 2, 9, true, [10.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let fc = 10.0 * C0 / (2.0 * core::f64::consts::PI);
assert!(s.angle(0.99 * fc).is_none());
let th = s.angle(2.0 * fc).unwrap();
assert!((th - 30f64.to_radians()).abs() < 1e-12);
}
}