use std::collections::HashMap;
use csx::discretize::MaterialGrid;
use csx::{CoordSystem, PropertyKind, Vec3};
use super::NodeMap;
use crate::axis::Axis;
use crate::engine::SheetEdge;
use crate::fdtd::{Cpml, Face};
#[derive(Debug, Default)]
pub(super) struct SheetMap {
pub edges: Vec<SheetEdge>,
pub pec: Vec<(usize, usize)>,
}
fn local_cell(lines: &[f64], i: usize) -> f64 {
let lo = if i > 0 { lines[i] - lines[i - 1] } else { 0.0 };
let hi = if i + 1 < lines.len() {
lines[i + 1] - lines[i]
} else {
0.0
};
lo.max(hi)
}
fn sheet_planes(
model: &csx::ContinuousStructure,
snap_tol: f64,
) -> HashMap<&str, Vec<(usize, usize)>> {
let mut out: HashMap<&str, Vec<(usize, usize)>> = HashMap::new();
for p in model.properties() {
if !matches!(p.kind, PropertyKind::ConductingSheet(_)) {
continue;
}
let planes = out.entry(p.name.as_str()).or_default();
for prim in &p.primitives {
if prim.dimension() != 2 {
continue;
}
let b = prim.bounding_box_in(CoordSystem::Cartesian, model.coord_system);
if b.is_empty() {
continue;
}
let tol = |a: usize, v: f64| {
let l = model.grid.lines(a);
model
.grid
.nearest_line(a, v)
.map_or(0.0, |i| snap_tol * local_cell(l, i))
+ 1e-12 * v.abs()
};
let flat: Vec<usize> = (0..3)
.filter(|&a| b.max[a] - b.min[a] <= tol(a, b.min[a]))
.collect();
if let [nrm] = flat[..]
&& let Some(k) = model.grid.nearest_line(nrm, b.min[nrm])
&& (model.grid.lines(nrm)[k] - b.min[nrm]).abs() <= tol(nrm, b.min[nrm])
{
planes.push((nrm, k));
}
}
}
out
}
fn shares(
model: &csx::ContinuousStructure,
d: usize,
node: [usize; 3],
nrm: usize,
) -> Option<(f64, f64)> {
let t = 3 - d - nrm;
let (ld, lt, ln) = (
model.grid.lines(d),
model.grid.lines(t),
model.grid.lines(nrm),
);
let sides = |l: &[f64], i: usize| {
let lo = (i > 0).then(|| (l[i] - 0.25 * (l[i] - l[i - 1]), 0.5 * (l[i] - l[i - 1])));
let hi =
(i + 1 < l.len()).then(|| (l[i] + 0.25 * (l[i + 1] - l[i]), 0.5 * (l[i + 1] - l[i])));
[lo, hi]
};
let mid = 0.5 * (ld[node[d]] + ld[node[d] + 1]);
let [dn, up] = sides(ln, node[nrm]);
let mut acc = [[0.0; 2]; 2]; for (side, s) in [dn, up].into_iter().enumerate() {
let Some((pn, wn)) = s else { continue };
for (pt, wt) in sides(lt, node[t]).into_iter().flatten() {
let mut p = Vec3::new(0.0, 0.0, 0.0);
p[d] = mid;
p[t] = pt;
p[nrm] = pn;
if let Some(m) = model.medium_at(p, CoordSystem::Cartesian) {
acc[side][0] += m.epsilon[d] * wn * wt;
acc[side][1] += m.kappa[d] * wn * wt;
}
}
}
let c = acc[0][0] + acc[1][0];
if c.is_nan() || c <= 0.0 {
return None;
}
let sc = acc[1][0] / c;
let g = acc[0][1] + acc[1][1];
let sg = if g > 0.0 { acc[1][1] / g } else { sc };
Some((sc, sg))
}
pub(super) fn sheet_edges(
model: &csx::ContinuousStructure,
mg: &MaterialGrid,
node_map: &NodeMap,
snap_tol: f64,
warnings: &mut Vec<String>,
) -> SheetMap {
let mut out = SheetMap::default();
if mg.sheets.is_empty() {
return out;
}
let planes = sheet_planes(model, snap_tol);
let mut idle = vec![0usize; mg.sheets.len()];
let mut pec = vec![0usize; mg.sheets.len()];
for d in 0..3 {
for (idx, &sid) in mg.sheet[d].iter().enumerate() {
if sid == 0 || mg.pec[d][idx] {
continue;
}
let s = sid as usize - 1;
let info = &mg.sheets[s];
let (sigma, t) = (info.conductivity, info.thickness);
if !(sigma.is_finite() && sigma > 0.0 && t.is_finite() && t > 0.0) {
idle[s] += 1;
continue;
}
let node = node_map.csx_node(idx);
let normal = planes.get(info.name.as_str()).and_then(|p| {
p.iter()
.find(|&&(nrm, k)| nrm != d && node[nrm] == k)
.map(|&(nrm, _)| nrm)
});
let Some(nrm) = normal else {
pec[s] += 1;
out.pec.push((d, idx));
continue;
};
out.edges.push(SheetEdge {
axis: Axis::ALL[d],
node,
normal: Axis::ALL[nrm],
conductivity: sigma,
thickness: t,
upper_share: shares(model, d, node, nrm),
});
}
}
for (s, info) in mg.sheets.iter().enumerate() {
if idle[s] > 0 {
warnings.push(format!(
"ConductingSheet {:?}: conductivity {} S/m and thickness {} m must be > 0; \
the sheet has no effect",
info.name, info.conductivity, info.thickness
));
}
if pec[s] > 0 {
warnings.push(format!(
"ConductingSheet {:?}: {} edge(s) not on a planar sheet (line, curve or volume \
primitive) approximated as PEC",
info.name, pec[s]
));
}
}
out
}
pub(super) fn pml_warning(
edges: &[SheetEdge],
nodes: [usize; 3],
pml: &[(Face, Cpml)],
warnings: &mut Vec<String>,
) {
let inside = edges
.iter()
.filter(|e| {
pml.iter().any(|(face, p)| {
let a = face.axis().index();
let along = usize::from(e.axis.index() == a);
if face.is_max() {
e.node[a] + along > nodes[a] - 1 - p.cells.min(nodes[a] - 1)
} else {
e.node[a] < p.cells
}
})
})
.count();
if inside > 0 {
warnings.push(format!(
"{inside} conducting-sheet edge(s) inside the PML do not get the PML correction; \
keep sheets out of the absorbing layers"
));
}
}