use crate::axis::Axis;
use crate::engine::{Engine, Fields, Hooks, Operator, StepContext};
use crate::fdtd::Face;
use crate::{Error, Result};
#[derive(Debug, Clone)]
struct Edge {
axis: Axis,
node: [usize; 3],
inner: [usize; 3],
k: f32,
saved: [f32; 2],
}
#[derive(Debug, Clone)]
struct Corner {
axis: Axis,
node: [usize; 3],
inner: [[usize; 3]; 2],
k: [f32; 2],
saved: [f32; 3],
}
#[derive(Debug, Clone)]
pub struct MurBoundary {
faces: Vec<(Face, f64)>,
edges: Vec<Edge>,
corners: Vec<Corner>,
}
impl MurBoundary {
pub fn new(engine: &Engine, face: Face, phase_velocity: f64) -> Result<Self> {
Self::with_faces(engine, &[(face, phase_velocity)])
}
pub fn with_faces(engine: &Engine, faces: &[(Face, f64)]) -> Result<Self> {
let grid = engine.grid();
let n = grid.nodes();
let dt = engine.dt();
let mut k_face = [None::<f32>; 6];
for &(face, v) in faces {
if !(v.is_finite() && v > 0.0) {
return Err(Error::Invalid(format!(
"Mur boundary on {face}: phase velocity {v} must be finite and > 0"
)));
}
if k_face[face.index()].is_some() {
return Err(Error::Invalid(format!(
"Mur boundary: face {face} listed twice"
)));
}
let a = face.axis();
if n[a.index()] < 3 {
return Err(Error::Invalid(format!(
"Mur boundary on {face}: needs at least 3 mesh lines along {a}, got {}",
n[a.index()]
)));
}
let delta = if face.is_max() {
grid.delta(a, n[a.index()] - 2)
} else {
grid.delta(a, 0)
};
k_face[face.index()] = Some(Self::coefficient(v, dt, delta) as f32);
}
let plane = |f: Face| {
if f.is_max() {
n[f.axis().index()] - 1
} else {
0
}
};
let inner = |f: Face, mut node: [usize; 3]| {
let a = f.axis().index();
node[a] = if f.is_max() { node[a] - 1 } else { node[a] + 1 };
node
};
let co = engine.coefficients();
let mut edges = Vec::new();
let mut corners = Vec::new();
for &(face, _) in faces {
let a = face.axis();
let k = k_face[face.index()].expect("set above");
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 (ca, cb) = (co.ca(t), co.cb(t));
for p in 0..n[t.index()] - 1 {
for q in 0..n[o.index()] {
let mut node = [0; 3];
node[a.index()] = plane(face);
node[t.index()] = p;
node[o.index()] = q;
let idx = grid.index(node);
if ca[idx] != 0.0 || cb[idx] != 0.0 {
return Err(Error::Invalid(format!(
"Mur boundary on {face}: the face must be built with \
FaceCondition::Pec (edge {t} at {node:?} is not)"
)));
}
let other = [false, true]
.map(|max| Face::new(o, max))
.into_iter()
.find(|&g| plane(g) == q && k_face[g.index()].is_some());
match other {
None => edges.push(Edge {
axis: t,
node,
inner: inner(face, node),
k,
saved: [0.0; 2],
}),
Some(g) if face.index() < g.index() => corners.push(Corner {
axis: t,
node,
inner: [inner(face, node), inner(g, node)],
k: [k, k_face[g.index()].expect("is Mur")],
saved: [0.0; 3],
}),
Some(_) => {}
}
}
}
}
}
Ok(Self {
faces: faces.to_vec(),
edges,
corners,
})
}
pub fn faces(&self) -> &[(Face, f64)] {
&self.faces
}
pub fn edge_count(&self) -> usize {
self.edges.len() + self.corners.len()
}
pub fn coefficient(v: f64, dt: f64, delta: f64) -> f64 {
(v * dt - delta) / (v * dt + delta)
}
}
impl Operator for MurBoundary {
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)];
}
for c in &mut self.corners {
c.saved = [
u.get(c.axis, c.node),
u.get(c.axis, c.inner[0]),
u.get(c.axis, c.inner[1]),
];
}
}
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;
let v = i + e.k * (u.get(e.axis, e.inner) - b);
u.set(e.axis, e.node, v);
}
for c in &self.corners {
let [b, i0, i1] = c.saved;
let v0 = i0 + c.k[0] * (u.get(c.axis, c.inner[0]) - b);
let v1 = i1 + c.k[1] * (u.get(c.axis, c.inner[1]) - b);
u.set(c.axis, c.node, 0.5 * (v0 + v1));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::{FaceCondition, Grid};
fn engine(faces: crate::engine::FaceConditions) -> Engine {
let g = Grid::uniform([4, 3, 5], [1e-3; 3]).unwrap();
Engine::builder(g).faces(faces).threads(1).build().unwrap()
}
#[test]
fn counts_and_validation() {
let e = engine(Default::default());
let m = MurBoundary::new(&e, Face::XMin, 3e8).unwrap();
assert_eq!(m.edge_count(), 3 * 6 + 4 * 5);
assert_eq!(m.corners.len(), 0);
let m = MurBoundary::with_faces(&e, &[(Face::XMin, 3e8), (Face::ZMin, 3e8)]).unwrap();
assert_eq!(m.corners.len(), 3);
assert_eq!(m.edge_count(), (3 * 6 + 4 * 5) + (4 * 4 + 5 * 3) - 3);
let all: Vec<_> = Face::ALL.iter().map(|&f| (f, 3e8)).collect();
let m = MurBoundary::with_faces(&e, &all).unwrap();
assert_eq!(m.corners.len(), 4 * (4 + 3 + 5));
assert!(MurBoundary::new(&e, Face::XMin, 0.0).is_err());
assert!(MurBoundary::new(&e, Face::XMin, f64::NAN).is_err());
assert!(MurBoundary::with_faces(&e, &[(Face::YMax, 1.0), (Face::YMax, 1.0)]).is_err());
let e =
engine(crate::engine::FaceConditions::default().with(Face::ZMax, FaceCondition::Pmc));
assert!(MurBoundary::new(&e, Face::ZMax, 3e8).is_err());
assert!(MurBoundary::new(&e, Face::ZMin, 3e8).is_ok());
let thin = Engine::builder(Grid::uniform([1, 3, 3], [1e-3; 3]).unwrap())
.threads(1)
.build()
.unwrap();
assert!(MurBoundary::new(&thin, Face::XMin, 3e8).is_err());
}
#[test]
fn local_cell_size() {
let g = Grid::new([
vec![0.0, 1e-3, 3e-3, 3.5e-3],
vec![0.0, 1e-3],
vec![0.0, 1e-3],
])
.unwrap();
let e = Engine::builder(g).threads(1).build().unwrap();
let m = MurBoundary::with_faces(&e, &[(Face::XMin, 2e8), (Face::XMax, 2e8)]).unwrap();
for ed in &m.edges {
let (delta, inner) = if ed.node[0] == 0 {
(1e-3, 1)
} else {
(0.5e-3, 2)
};
assert_eq!(ed.inner[0], inner);
assert_eq!(ed.k, MurBoundary::coefficient(2e8, e.dt(), delta) as f32);
}
assert_eq!(m.edges.len(), 2 * (2 + 2));
}
#[test]
fn coefficient() {
assert_eq!(MurBoundary::coefficient(1.0, 2.0, 2.0), 0.0);
let k = MurBoundary::coefficient(3e8, 1e-12, 1e-3);
assert!(k < 0.0 && k > -1.0);
}
}