use crate::axis::Axis;
use crate::constants::C0;
use crate::engine::{EdgeMaterials, Engine, Fields, Hooks, Operator, StepContext};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AbcSheet {
pub normal: Axis,
pub plane: usize,
pub positive: bool,
pub start: [usize; 3],
pub stop: [usize; 3],
}
#[derive(Debug, Clone, Copy)]
pub enum SheetVelocity<'a> {
Fixed(f64),
Local(&'a EdgeMaterials),
}
#[derive(Debug, Clone)]
struct Edge {
axis: Axis,
node: [usize; 3],
inner: [usize; 3],
k: f32,
saved: [f32; 2],
}
#[derive(Debug, Clone)]
pub struct AbsorbingSheet {
sheet: AbcSheet,
edges: Vec<Edge>,
}
impl AbsorbingSheet {
pub fn new(engine: &Engine, sheet: &AbcSheet, velocity: SheetVelocity<'_>) -> Result<Self> {
let grid = engine.grid();
let n = grid.nodes();
let a = sheet.normal;
let ai = a.index();
let s = *sheet;
if s.plane >= n[ai] {
return Err(Error::Invalid(format!(
"absorbing sheet: plane {} outside the mesh ({} lines along {a})",
s.plane, n[ai]
)));
}
let inner_plane = if s.positive {
s.plane.checked_sub(1)
} else {
Some(s.plane + 1).filter(|&p| p < n[ai])
}
.ok_or_else(|| {
Error::Invalid(format!(
"absorbing sheet at {a} plane {}: no mesh plane on its absorbing side",
s.plane
))
})?;
for d in Axis::ALL.into_iter().filter(|&d| d != a) {
let di = d.index();
if s.start[di] > s.stop[di] || s.stop[di] >= n[di] {
return Err(Error::Invalid(format!(
"absorbing sheet: range {}..={} along {d} invalid for {} lines",
s.start[di], s.stop[di], n[di]
)));
}
}
if let SheetVelocity::Local(m) = velocity
&& m.nodes() != n
{
return Err(Error::Invalid(
"absorbing sheet: materials do not match the grid".into(),
));
}
let lo = s.plane.min(inner_plane);
let delta = grid.delta(a, lo);
let dt = engine.dt();
let outer = s.plane == 0 || s.plane == n[ai] - 1;
let co = engine.coefficients();
let mut edges = Vec::new();
for t in Axis::ALL.into_iter().filter(|&t| t != a) {
let o = Axis::ALL
.into_iter()
.find(|&x| x != a && x != t)
.expect("three axes");
let (ti, oi) = (t.index(), o.index());
let (ca, cb) = (co.ca(t), co.cb(t));
for p in s.start[ti]..s.stop[ti] {
for q in s.start[oi]..=s.stop[oi] {
let mut node = [0; 3];
node[ai] = s.plane;
node[ti] = p;
node[oi] = q;
let idx = grid.index(node);
if !outer && ca[idx] == 0.0 && cb[idx] == 0.0 {
continue;
}
let mut inner = node;
inner[ai] = inner_plane;
let v = match velocity {
SheetVelocity::Fixed(v) => v,
SheetVelocity::Local(m) => {
let mut face = node;
face[ai] = lo;
let eps = m.eps_r(t)[grid.index(inner)];
let mu = m.mu_r(o)[grid.index(face)];
C0 / (eps * mu).sqrt()
}
};
if !(v.is_finite() && v > 0.0) {
return Err(Error::Invalid(format!(
"absorbing sheet: phase velocity {v} must be finite and > 0"
)));
}
edges.push(Edge {
axis: t,
node,
inner,
k: ((v * dt - delta) / (v * dt + delta)) as f32,
saved: [0.0; 2],
});
}
}
}
Ok(Self { sheet: s, edges })
}
pub fn sheet(&self) -> &AbcSheet {
&self.sheet
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
}
impl Operator for AbsorbingSheet {
fn hooks(&self) -> Hooks {
Hooks::PRE_E | Hooks::POST_E
}
fn pre_e(&mut self, fields: &mut Fields<'_>, _ctx: &StepContext<'_>) {
let u = &fields.u;
for e in &mut self.edges {
e.saved = [u.get(e.axis, e.node), u.get(e.axis, e.inner)];
}
}
fn post_e(&mut self, fields: &mut Fields<'_>, _ctx: &StepContext<'_>) {
let u = &mut fields.u;
for e in &self.edges {
let [b, i] = e.saved;
u.set(e.axis, e.node, i + e.k * (u.get(e.axis, e.inner) - b));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::Grid;
fn engine() -> Engine {
let g = Grid::uniform([6, 3, 4], [1e-3; 3]).unwrap();
Engine::builder(g).threads(1).build().unwrap()
}
#[test]
fn counts_and_validation() {
let e = engine();
let full = AbcSheet {
normal: Axis::X,
plane: 3,
positive: true,
start: [0; 3],
stop: [0, 3, 4],
};
let op = AbsorbingSheet::new(&e, &full, SheetVelocity::Fixed(C0)).unwrap();
assert_eq!(op.edge_count(), 3 * 3 + 4 * 2);
assert!(op.edges.iter().all(|ed| ed.inner[0] == 2));
let neg = AbcSheet {
positive: false,
..full
};
let op = AbsorbingSheet::new(&e, &neg, SheetVelocity::Fixed(C0)).unwrap();
assert!(op.edges.iter().all(|ed| ed.inner[0] == 4));
let face = AbcSheet {
plane: 0,
positive: false,
..full
};
let op = AbsorbingSheet::new(&e, &face, SheetVelocity::Fixed(C0)).unwrap();
assert_eq!(op.edge_count(), 3 * 5 + 4 * 4);
let bad = |s: AbcSheet| AbsorbingSheet::new(&e, &s, SheetVelocity::Fixed(C0)).is_err();
assert!(bad(AbcSheet {
plane: 0,
positive: true,
..full
}));
assert!(bad(AbcSheet {
plane: 6,
positive: false,
..full
}));
assert!(bad(AbcSheet {
stop: [0, 4, 4],
..full
}));
assert!(AbsorbingSheet::new(&e, &full, SheetVelocity::Fixed(-1.0)).is_err());
}
#[test]
fn local_velocity() {
let e = engine();
let mut m = EdgeMaterials::vacuum(e.grid());
m.eps_r_mut(Axis::Y).fill(4.0);
let s = AbcSheet {
normal: Axis::X,
plane: 3,
positive: true,
start: [0, 1, 1],
stop: [0, 2, 2],
};
let op = AbsorbingSheet::new(&e, &s, SheetVelocity::Local(&m)).unwrap();
let k = |v: f64| ((v * e.dt() - 1e-3) / (v * e.dt() + 1e-3)) as f32;
for ed in &op.edges {
let v = if ed.axis == Axis::Y { C0 / 2.0 } else { C0 };
assert_eq!(ed.k, k(v), "{ed:?}");
}
assert_eq!(op.edge_count(), 2 + 2);
}
}