use crate::axis::Axis;
use crate::constants::{EPS0, Z0};
use crate::engine::grid::Grid;
use crate::engine::kernel::flush;
use crate::engine::slab::{SlabMut, SlabRef};
use crate::fdtd::Cpml;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Len {
a: f64,
b: f64,
}
impl Len {
fn add(self, o: Len) -> Len {
Len {
a: self.a + o.a,
b: self.b + o.b,
}
}
fn sub(self, o: Len) -> Len {
Len {
a: self.a - o.a,
b: self.b - o.b,
}
}
fn scale(self, f: f64) -> Len {
Len {
a: self.a * f,
b: self.b * f,
}
}
}
const ONE: Len = Len { a: 1.0, b: 0.0 };
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct Stage {
k: f32,
beta: f32,
w: f32,
}
impl Stage {
const IDENTITY: Stage = Stage {
k: 1.0,
beta: 0.0,
w: 0.0,
};
fn ratio(num: Len, den: Len, alpha: f64, dt: f64) -> Stage {
let (a, b, c, d) = (num.a, num.b, den.a, den.b);
let k = a / c;
let gamma = (alpha + d / c) / EPS0;
let r = (b - a * d / c) / (c * EPS0);
let x = gamma * dt;
let (beta, w) = ((2.0 - x) / (2.0 + x), r * dt / (2.0 + x));
Stage {
k: k as f32,
beta: beta as f32,
w: w as f32,
}
}
}
struct Profile<'a> {
rho: &'a [f64],
inner: usize,
cells: f64,
m: f64,
sigma_tot: f64,
kappa_cum: Vec<f64>,
kappa_max: f64,
}
impl<'a> Profile<'a> {
fn new(rho: &'a [f64], inner: usize, p: &Cpml, n_r: f64) -> Self {
let cells = p.cells as f64;
let m = p.grading_order;
let sigma_tot = p.sigma_max(n_r * Z0, 1.0) * cells / (m + 1.0);
let mut s = Self {
rho,
inner,
cells,
m,
sigma_tot,
kappa_cum: vec![0.0; rho.len()],
kappa_max: p.kappa_max,
};
for c in 0..rho.len() - 1 {
let dk = s.kappa_in_cell(c, c as f64 + 1.0);
s.kappa_cum[c + 1] = s.kappa_cum[c] + dk;
}
s
}
fn t(&self, x: f64) -> f64 {
((x - self.inner as f64) / self.cells).clamp(0.0, 1.0)
}
fn kappa_in_cell(&self, c: usize, x: f64) -> f64 {
let d = self.rho[c + 1] - self.rho[c];
let e = self.m + 1.0;
d * (self.kappa_max - 1.0) * self.cells / e * (self.t(x).powf(e) - self.t(c as f64).powf(e))
}
fn cell(&self, x: f64) -> usize {
(x.floor() as usize).min(self.rho.len() - 2)
}
fn rho(&self, x: f64) -> f64 {
let c = self.cell(x);
self.rho[c] + (x - c as f64) * (self.rho[c + 1] - self.rho[c])
}
fn tilde(&self, x: f64) -> Len {
let c = self.cell(x);
let k = self.kappa_cum[c] + self.kappa_in_cell(c, x);
Len {
a: self.rho(x) + k,
b: self.sigma_tot * self.t(x).powf(self.m + 1.0),
}
}
fn sbar(&self, x: f64) -> Len {
self.tilde(x).scale(1.0 / self.rho(x))
}
fn s(&self, xa: f64, xb: f64) -> Len {
self.tilde(xb)
.sub(self.tilde(xa))
.scale(1.0 / (self.rho(xb) - self.rho(xa)))
}
fn sbar2(&self, xa: f64, xb: f64) -> Len {
self.tilde(xa)
.add(self.tilde(xb))
.scale(1.0 / (self.rho(xa) + self.rho(xb)))
}
}
fn stages(
prof: &Profile<'_>,
comp: Axis,
e_side: bool,
i: usize,
alpha: f64,
dt: f64,
) -> [Stage; 2] {
let last = (prof.rho.len() - 1) as f64;
let x = i as f64;
let cell = (x, x + 1.0);
let dual = ((x - 0.5).max(0.0), (x + 0.5).min(last));
let r = |n: Len, d: Len| Stage::ratio(n, d, alpha, dt);
match (comp, e_side) {
(Axis::X, true) => [
r(prof.s(cell.0, cell.1), prof.sbar(x + 0.5)),
Stage::IDENTITY,
],
(Axis::X, false) => [r(prof.s(dual.0, dual.1), prof.sbar(x)), Stage::IDENTITY],
(Axis::Y, true) => [r(prof.sbar(x), prof.s(dual.0, dual.1)), Stage::IDENTITY],
(Axis::Y, false) => [
r(prof.sbar(x + 0.5), prof.s(cell.0, cell.1)),
Stage::IDENTITY,
],
(Axis::Z, _) => {
let (a, b) = if e_side { dual } else { cell };
[r(ONE, prof.sbar2(a, b)), r(ONE, prof.s(a, b))]
}
}
}
pub const ALPHA_FLOOR: f64 = 1e-3;
pub(crate) fn alpha(grid: &Grid, p: &Cpml, n_r: f64) -> f64 {
let n = grid.nodes()[0];
let d = grid.delta(Axis::X, n - 1 - p.cells);
p.alpha_max.max(ALPHA_FLOOR * p.sigma_max(n_r * Z0, d))
}
pub(crate) type Transverse = [Vec<f32>; 3];
#[derive(Debug, Clone)]
struct Comp {
comp: Axis,
e_side: bool,
x: (usize, usize),
j: (usize, usize),
k: (usize, usize),
st: Vec<[Stage; 2]>,
tr: [Option<Transverse>; 2],
psi: Vec<[f32; 6]>,
}
#[derive(Debug, Clone)]
pub(crate) struct CylLayer {
e: Vec<Comp>,
h: Vec<Comp>,
}
impl CylLayer {
pub(crate) fn new(
grid: &Grid,
dt: f64,
p: &Cpml,
n_r: f64,
mut transverse: impl FnMut(bool, Axis, Axis) -> Option<Transverse>,
) -> Self {
let n = grid.nodes();
let rho = grid.lines(Axis::X);
let inner = n[0] - 1 - p.cells;
let prof = Profile::new(rho, inner, p, n_r);
let alpha = alpha(grid, p, n_r);
let mut comp = |a: Axis, e_side: bool| {
let full = |d: Axis| {
let pad = if e_side { d == a } else { d != a };
(0, n[d.index()] - usize::from(pad))
};
let mut x = full(Axis::X);
x.0 = inner;
let st = (x.0..x.1)
.map(|i| stages(&prof, a, e_side, i, alpha, dt))
.collect();
let (nx, pv) = (a.next(), a.prev());
let tr = [nx, pv].map(|d| {
if d == Axis::X {
None
} else {
transverse(e_side, a, d)
}
});
Comp {
comp: a,
e_side,
x,
j: full(Axis::Y),
k: full(Axis::Z),
st,
tr,
psi: Vec::new(),
}
};
let e = Axis::ALL.map(|a| comp(a, true)).to_vec();
let h = Axis::ALL.map(|a| comp(a, false)).to_vec();
Self { e, h }
}
pub(crate) fn clip(&self, x0: usize, x1: usize) -> Self {
let clip = |c: &Comp| {
let (lo, hi) = (c.x.0.max(x0), c.x.1.min(x1));
(lo < hi).then(|| {
let len = (hi - lo) * (c.j.1 - c.j.0) * (c.k.1 - c.k.0);
Comp {
x: (lo, hi),
st: c.st[lo - c.x.0..hi - c.x.0].to_vec(),
psi: vec![[0.0; 6]; len],
..c.clone()
}
})
};
Self {
e: self.e.iter().filter_map(clip).collect(),
h: self.h.iter().filter_map(clip).collect(),
}
}
pub(crate) fn post_e<'c>(
&mut self,
u: &mut SlabMut<'_>,
i: &SlabRef<'_>,
coef: impl Fn(Axis) -> &'c [f32],
zero: &[f32],
) {
for c in &mut self.e {
c.apply(u, i, coef(c.comp), zero);
}
}
pub(crate) fn post_h<'c>(
&mut self,
i: &mut SlabMut<'_>,
u: &SlabRef<'_>,
coef: impl Fn(Axis) -> &'c [f32],
zero: &[f32],
) {
for c in &mut self.h {
c.apply(i, u, coef(c.comp), zero);
}
}
}
#[allow(clippy::too_many_arguments)]
fn deriv(
out: &mut [f32],
src: &SlabRef<'_>,
zero: &[f32],
c: Axis,
d: Axis,
i: usize,
j: usize,
k0: usize,
e_side: bool,
) {
let nz = src.nodes()[2];
let o = j * nz + k0;
let len = out.len();
let cur = src.plane(c, i);
match d {
Axis::X => {
if e_side {
let prev = if i == 0 { zero } else { src.plane(c, i - 1) };
for (q, v) in out.iter_mut().enumerate() {
*v = cur[o + q] - prev[o + q];
}
} else {
let next = src.plane(c, i + 1);
for (q, v) in out.iter_mut().enumerate() {
*v = next[o + q] - cur[o + q];
}
}
}
Axis::Y => {
if !e_side {
let (h, l) = (&cur[o + nz..o + nz + len], &cur[o..o + len]);
for (v, (a, b)) in out.iter_mut().zip(h.iter().zip(l)) {
*v = a - b;
}
} else if j == 0 {
out.copy_from_slice(&cur[o..o + len]);
} else {
let (h, l) = (&cur[o..o + len], &cur[o - nz..o - nz + len]);
for (v, (a, b)) in out.iter_mut().zip(h.iter().zip(l)) {
*v = a - b;
}
}
}
Axis::Z => {
for (q, v) in out.iter_mut().enumerate() {
let p = o + q;
*v = if !e_side {
cur[p + 1] - cur[p]
} else if k0 + q == 0 {
cur[p]
} else {
cur[p] - cur[p - 1]
};
}
}
}
}
impl Comp {
fn apply(&mut self, tgt: &mut SlabMut<'_>, src: &SlabRef<'_>, coef: &[f32], zero: &[f32]) {
let n = src.nodes();
let (nz, pl) = (n[2], n[1] * n[2]);
let (a, e_side) = (self.comp, self.e_side);
let (dn, dp) = (a.next(), a.prev());
let scale: f32 = if e_side { 1.0 } else { -1.0 };
let len = self.k.1 - self.k.0;
let (mut d1, mut d2) = (vec![0.0f32; len], vec![0.0f32; len]);
let mut q = 0;
for i in self.x.0..self.x.1 {
let [s0, s1] = self.st[i - self.x.0];
let cf = &coef[i * pl..(i + 1) * pl];
for j in self.j.0..self.j.1 {
deriv(&mut d1, src, zero, dp, dn, i, j, self.k.0, e_side);
deriv(&mut d2, src, zero, dn, dp, i, j, self.k.0, e_side);
let o = j * nz + self.k.0;
let t = &mut tgt.plane_mut(a, i)[o..o + len];
let cf = &cf[o..o + len];
let psi = &mut self.psi[q..q + len];
q += len;
let tco = |which: usize, d: Axis, kk: usize| -> [f32; 3] {
match &self.tr[which] {
None => [0.0; 3],
Some(v) => {
let ix = if d == Axis::Y { j } else { self.k.0 + kk };
[v[0][ix], v[1][ix], v[2][ix]]
}
}
};
for kk in 0..len {
let (x1, x2) = (d1[kk], d2[kk]);
let s = x1 - x2;
let ps = &mut psi[kk];
let [b1, w1, k1] = tco(0, dn, kk);
ps[4] = flush(b1 * ps[4] + w1 * x1);
let y1 = x1 + k1 * x1 + ps[4];
let [b2, w2, k2] = tco(1, dp, kk);
ps[5] = flush(b2 * ps[5] + w2 * x2);
let y2 = x2 + k2 * x2 + ps[5];
let sp = y1 - y2;
ps[0] = flush(s0.beta * ps[0] + s0.w * (sp + ps[1]));
ps[1] = sp;
let y = s0.k * sp + ps[0];
ps[2] = flush(s1.beta * ps[2] + s1.w * (y + ps[3]));
ps[3] = y;
let y = s1.k * y + ps[2];
t[kk] = flush(t[kk] + scale * cf[kk] * (y - s));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::f64::consts::TAU;
fn mul(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
(a.0 * b.0 - a.1 * b.1, a.0 * b.1 + a.1 * b.0)
}
fn div(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
let d = b.0 * b.0 + b.1 * b.1;
((a.0 * b.0 + a.1 * b.1) / d, (a.1 * b.0 - a.0 * b.1) / d)
}
#[test]
fn stage_is_exact_at_the_warped_frequency() {
let (dt, alpha) = (1e-12, 0.05);
let cases = [
(ONE, Len { a: 2.0, b: 3.0 }),
(Len { a: 1.5, b: 4.0 }, Len { a: 1.1, b: 0.2 }),
(Len { a: 1.0, b: 2.0 }, ONE),
];
for (num, den) in cases {
let st = Stage::ratio(num, den, alpha, dt);
let (k, beta, w) = (st.k as f64, st.beta as f64, st.w as f64);
for f in [1e8, 3e9, 3e10, 1e11] {
let wdt = TAU * f * dt;
let zi = ((-wdt).cos(), (-wdt).sin());
let h = div(
mul((w, 0.0), (1.0 + zi.0, zi.1)),
(1.0 - beta * zi.0, -beta * zi.1),
);
let h = (k + h.0, h.1);
let p = (alpha, 2.0 / dt * (wdt / 2.0).tan() * EPS0);
let want = div(
(num.a * p.0 + num.b, num.a * p.1),
(den.a * p.0 + den.b, den.a * p.1),
);
let err = (h.0 - want.0).hypot(h.1 - want.1) / want.0.hypot(want.1);
assert!(err < 1e-5, "{num:?}/{den:?} at {f:e}: {h:?} vs {want:?}");
}
}
}
#[test]
fn profile_integrals() {
let rho: Vec<f64> = (0..=20).map(|i| 0.01 + i as f64 * 1e-3).collect();
let p = Cpml::cells(8).kappa_max(3.0);
let prof = Profile::new(&rho, 12, &p, 1.0);
assert_eq!(prof.tilde(12.0), Len { a: rho[12], b: 0.0 });
assert_eq!(prof.sbar(5.5), ONE);
let t = prof.tilde(20.0);
let smax = p.sigma_max(Z0, 1e-3);
let want_s = smax * 8e-3 / 4.0;
assert!((t.b / want_s - 1.0).abs() < 1e-12);
let want_k = rho[20] + 2.0 * 8e-3 / 4.0;
assert!((t.a / want_k - 1.0).abs() < 1e-12);
let s = prof.s(19.0, 20.0);
let mean_t3: f64 = (8f64.powi(4) - 7f64.powi(4)) / 4.0 / 8f64.powi(3);
assert!((s.b / (smax * mean_t3) - 1.0).abs() < 1e-12);
}
}