use std::collections::BTreeMap;
use super::edge_material::EdgeMaterials;
use super::grid::Grid;
use crate::axis::Axis;
use crate::{Error, Result};
pub fn lattice_radius(hx: f64, hy: f64) -> f64 {
assert!(
hx.is_finite() && hx > 0.0 && hy.is_finite() && hy > 0.0,
"cell sizes must be finite and positive"
);
let (cx, cy) = (hy / hx, hx / hy);
let r = |m: f64| {
let f = |th: f64| {
let a = 2.0 * cx + 2.0 * cy * (1.0 - th.cos());
let b = 2.0 * cx;
let amb = 4.0 * cy * (0.5 * th).sin().powi(2);
let root = (amb * (a + b)).sqrt();
let rho = (a - root) / b;
(1.0 - rho.powf(m)) / root * th
};
let s_max = (std::f64::consts::PI * m * 1e9).ln();
let n = 8000; let h = s_max / n as f64;
let mut sum = f(std::f64::consts::PI) + f(std::f64::consts::PI * (-s_max).exp());
for i in 1..n {
let th = std::f64::consts::PI * (-(i as f64) * h).exp();
sum += if i % 2 == 1 { 4.0 } else { 2.0 } * f(th);
}
sum * h / 3.0 / std::f64::consts::PI
};
let ln_ratio = |m: f64| (m * hx).ln() - 2.0 * std::f64::consts::PI * r(m);
let (l1, l2) = (ln_ratio(64.0), ln_ratio(128.0));
((4.0 * l2 - l1) / 3.0).exp()
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThinWire {
axis: Axis,
start: [usize; 3],
segments: usize,
radius: f64,
}
impl ThinWire {
pub fn new(axis: Axis, start: [usize; 3], segments: usize, radius: f64) -> Self {
Self {
axis,
start,
segments,
radius,
}
}
pub fn axis(&self) -> Axis {
self.axis
}
pub fn start(&self) -> [usize; 3] {
self.start
}
pub fn segments(&self) -> usize {
self.segments
}
pub fn end(&self) -> [usize; 3] {
let mut e = self.start;
e[self.axis.index()] += self.segments;
e
}
pub fn radius(&self) -> f64 {
self.radius
}
fn sides(&self, grid: &Grid, t: Axis) -> Result<(f64, f64)> {
let i = self.start[t.index()];
let n = grid.nodes()[t.index()];
let lo = (i > 0).then(|| grid.delta(t, i - 1));
let hi = (i + 1 < n).then(|| grid.delta(t, i));
match (lo, hi) {
(Some(a), Some(b)) => Ok((a, b)),
(Some(a), None) => Ok((a, a)),
(None, Some(b)) => Ok((b, b)),
(None, None) => Err(Error::Invalid(format!(
"thin wire: the grid has a single line along {t}"
))),
}
}
fn check(&self, grid: &Grid) -> Result<()> {
let n = grid.nodes();
let a = self.axis.index();
if self.segments == 0 || (0..3).any(|d| self.start[d] >= n[d]) {
return Err(Error::Invalid(format!(
"thin wire at {:?} with {} segments is empty or outside the grid",
self.start, self.segments
)));
}
if self.start[a] + self.segments >= n[a] {
return Err(Error::Invalid(format!(
"thin wire from {:?} with {} segments along {} leaves the grid",
self.start, self.segments, self.axis
)));
}
if !(self.radius.is_finite() && self.radius > 0.0) {
return Err(Error::Invalid(format!(
"thin wire radius {} must be finite and positive",
self.radius
)));
}
Ok(())
}
pub fn staircase_radius(&self, grid: &Grid) -> Result<f64> {
self.check(grid)?;
let (b, c) = self.axis.others();
let (b0, b1) = self.sides(grid, b)?;
let (c0, c1) = self.sides(grid, c)?;
let harm = |x: f64, y: f64| 2.0 / (1.0 / x + 1.0 / y);
Ok(lattice_radius(harm(b0, b1), harm(c0, c1)))
}
pub fn factor(&self, grid: &Grid) -> Result<f64> {
let r_eq = self.staircase_radius(grid)?;
let (b, c) = self.axis.others();
let (b0, b1) = self.sides(grid, b)?;
let (c0, c1) = self.sides(grid, c)?;
let (lb, lc) = (0.5 * (b0 + b1), 0.5 * (c0 + c1));
let sum_c = lc * (1.0 / b0 + 1.0 / b1) + lb * (1.0 / c0 + 1.0 / c1);
Ok(1.0 + sum_c / (2.0 * std::f64::consts::PI) * (r_eq / self.radius).ln())
}
pub fn apply(&self, grid: &Grid, mats: &mut EdgeMaterials) -> Result<f64> {
if mats.nodes() != grid.nodes() {
return Err(Error::Invalid(format!(
"edge material map has {:?} nodes, grid has {:?}",
mats.nodes(),
grid.nodes()
)));
}
let g = self.factor(grid)?;
let r_eq = self.staircase_radius(grid)?;
if g <= 1.0 {
return Err(Error::Invalid(format!(
"thin wire radius {:e} m is not below the staircase radius {r_eq:e} m",
self.radius
)));
}
self.scale(grid, mats, g, r_eq, false);
Ok(g)
}
pub fn remove(&self, grid: &Grid, mats: &mut EdgeMaterials) -> Result<()> {
let g = self.factor(grid)?;
let r_eq = self.staircase_radius(grid)?;
if g <= 1.0 || mats.nodes() != grid.nodes() {
return Err(Error::Invalid("thin wire: nothing to remove".into()));
}
self.scale(grid, mats, g, r_eq, true);
Ok(())
}
fn scale(&self, grid: &Grid, mats: &mut EdgeMaterials, g: f64, r_eq: f64, inverse: bool) {
let (edges, faces) = self.plan(grid, mats, g, self.radius / r_eq);
let inv = |f: f64| if inverse { 1.0 / f } else { f };
for ((a, node), f) in edges {
let f = inv(f);
let t = Axis::ALL[a];
if !mats.is_pec_edge(t, node) {
let i = grid.index(node);
let (e, k) = (mats.eps_r(t)[i], mats.kappa(t)[i]);
mats.set_edge(t, node, e * f, k * f);
}
}
for ((a, node), f) in faces {
let f = inv(f);
let t = Axis::ALL[a];
let i = grid.index(node);
let (m, s) = (mats.mu_r(t)[i], mats.sigma_m(t)[i]);
mats.set_face(t, node, m * f, s * f);
}
}
pub fn open_ends(&self, mats: &EdgeMaterials) -> [bool; 2] {
let a = self.axis.index();
let n = mats.nodes();
let start = self.start[a] > 0 && {
let mut q = self.start;
q[a] -= 1;
!mats.is_pec_edge(self.axis, q)
};
let end =
self.start[a] + self.segments + 1 < n[a] && !mats.is_pec_edge(self.axis, self.end());
[start, end]
}
pub fn set_pec(&self, mats: &mut EdgeMaterials, except: &[usize]) {
for s in (0..self.segments).filter(|s| !except.contains(s)) {
mats.set_pec_edge(self.axis, self.node(s as isize));
}
}
fn node(&self, s: isize) -> [usize; 3] {
let mut p = self.start;
let a = self.axis.index();
p[a] = (p[a] as isize + s) as usize;
p
}
#[allow(clippy::type_complexity)]
fn plan(
&self,
grid: &Grid,
mats: &EdgeMaterials,
g: f64,
c: f64,
) -> (
BTreeMap<(usize, [usize; 3]), f64>,
BTreeMap<(usize, [usize; 3]), f64>,
) {
let n = grid.nodes();
let a = self.axis.index();
let (b, cc) = self.axis.others();
let mut edges: BTreeMap<(usize, [usize; 3]), f64> = BTreeMap::new();
let mut faces: BTreeMap<(usize, [usize; 3]), f64> = BTreeMap::new();
let mut edge = |t: usize, p: [usize; 3], f: f64| {
let e = edges.entry((t, p)).or_insert(f);
*e = e.min(f);
};
let mut face = |t: usize, p: [usize; 3], f: f64| {
let e = faces.entry((t, p)).or_insert(f);
*e = e.max(f);
};
let pair = |p: [usize; 3], u: usize| {
let fwd = (p[u] + 1 < n[u]).then_some(p);
let back = (p[u] > 0).then(|| {
let mut q = p;
q[u] -= 1;
q
});
fwd.into_iter().chain(back)
};
let radial = |p: [usize; 3]| {
[b.index(), cc.index()]
.into_iter()
.flat_map(move |t| pair(p, t).map(move |q| (t, q)))
};
let seg_faces = |p: [usize; 3]| {
[(b.index(), cc.index()), (cc.index(), b.index())]
.into_iter()
.flat_map(move |(t, u)| pair(p, u).map(move |q| (t, q)))
};
let axial = |p: [usize; 3]| {
pair(p, b.index())
.flat_map(move |q| pair(q, cc.index()))
.filter(move |q| {
q[b.index()] + 1 < n[b.index()] && q[cc.index()] + 1 < n[cc.index()]
})
.map(move |q| (a, q))
};
let [open_start, open_end] = self.open_ends(mats);
let last = self.segments as isize - 1;
for s in 1..self.segments as isize {
for (t, q) in radial(self.node(s)) {
edge(t, q, 1.0 / g);
}
for (t, q) in axial(self.node(s)) {
face(t, q, g);
}
}
for s in 0..=last {
let half = (s == 0 && open_start) || (s == last && open_end);
for (t, q) in seg_faces(self.node(s)) {
face(t, q, if half { 2.0 * g } else { g });
}
}
let caps = [
(open_start, 0, -1),
(open_end, self.segments as isize, self.segments as isize),
];
for (open, end, beyond) in caps {
if !open {
continue;
}
for (t, q) in radial(self.node(end)) {
edge(t, q, 0.5 / g);
}
for (t, q) in axial(self.node(end)) {
face(t, q, 2.0 * g);
}
edge(a, self.node(beyond), c);
for (t, q) in seg_faces(self.node(beyond)) {
face(t, q, (2.0 * g).max(1.0 / c));
}
}
(edges, faces)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn square_lattice_radius() {
let want = (-core::f64::consts::EULER_GAMMA).exp() / (2.0 * 2f64.sqrt());
let r = lattice_radius(1.0, 1.0);
assert!((r - want).abs() < 1e-6 * want, "{r} vs {want}");
assert!((lattice_radius(2e-3, 2e-3) / 2e-3 - want).abs() < 1e-6 * want);
}
#[test]
fn rectangular_lattice_radius_is_symmetric() {
let (a, b) = (lattice_radius(1.0, 2.0), lattice_radius(2.0, 1.0));
assert!((a - b).abs() < 1e-6 * a, "{a} {b}");
assert!(a > lattice_radius(1.0, 1.0) && a < lattice_radius(2.0, 2.0));
}
#[test]
fn factor_and_elements() {
let grid = Grid::uniform([10, 10, 10], [1e-3; 3]).unwrap();
let r_eq = lattice_radius(1e-3, 1e-3);
let w = ThinWire::new(Axis::Z, [5, 5, 2], 4, r_eq / 10.0);
let g = w.factor(&grid).unwrap();
assert!((g - (1.0 + 2.0 / std::f64::consts::PI * 10f64.ln())).abs() < 1e-12);
let mut m = EdgeMaterials::vacuum(&grid);
w.set_pec(&mut m, &[2]);
assert!(m.is_pec_edge(Axis::Z, [5, 5, 2]) && !m.is_pec_edge(Axis::Z, [5, 5, 4]));
assert_eq!(w.open_ends(&m), [true, true]);
let (edges, faces) = w.plan(&grid, &m, g, 0.1);
assert_eq!(edges.len(), 5 * 4 + 2);
assert_eq!(faces.len(), 6 * 4 + 5 * 4);
assert_eq!(w.apply(&grid, &mut m).unwrap(), g);
let eps = |t: Axis, p: [usize; 3]| m.eps_r(t)[grid.index(p)];
let mu = |t: Axis, p: [usize; 3]| m.mu_r(t)[grid.index(p)];
assert!((eps(Axis::X, [4, 5, 3]) - 1.0 / g).abs() < 1e-15);
assert!((eps(Axis::X, [5, 5, 2]) - 0.5 / g).abs() < 1e-15);
assert!((eps(Axis::Y, [5, 5, 6]) - 0.5 / g).abs() < 1e-15);
assert!((eps(Axis::Z, [5, 5, 1]) - 0.1).abs() < 1e-15);
assert!((eps(Axis::Z, [5, 5, 6]) - 0.1).abs() < 1e-15);
assert_eq!(mu(Axis::Y, [5, 5, 1]), 10.0);
assert_eq!(mu(Axis::Y, [5, 5, 2]), 2.0 * g);
assert_eq!(mu(Axis::X, [5, 4, 3]), g);
assert_eq!(mu(Axis::X, [5, 4, 5]), 2.0 * g);
assert_eq!(mu(Axis::Z, [4, 4, 3]), g);
assert_eq!(mu(Axis::Z, [4, 4, 2]), 2.0 * g);
assert_eq!(mu(Axis::Z, [4, 4, 1]), 1.0);
let mut m2 = EdgeMaterials::vacuum(&grid);
m2.set_pec_edge(Axis::Z, [5, 5, 1]);
assert_eq!(w.open_ends(&m2), [false, true]);
let thick = ThinWire::new(Axis::Z, [5, 5, 2], 4, 0.3e-3);
assert!(thick.apply(&grid, &mut m).is_err());
assert!(
ThinWire::new(Axis::Z, [5, 5, 2], 9, 1e-5)
.factor(&grid)
.is_err()
);
}
}