use crate::axis::Axis;
use crate::compile::PlaneWaveSource;
use crate::constants::C0;
use crate::engine::{PeriodicPlaneWaveSpec, PlaneWaveSpec};
use crate::fdtd::Fdtd;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct CellPlaneWave {
pub axis: Axis,
pub start: usize,
pub stop: usize,
}
fn periodic_axes(fdtd: &Fdtd) -> [bool; 3] {
Axis::ALL.map(|a| fdtd.get_boundaries().is_periodic(a))
}
pub(crate) fn cell_geometry(
spec: &PlaneWaveSpec,
nodes: [usize; 3],
periodic: [bool; 3],
) -> Option<CellPlaneWave> {
if !periodic.iter().any(|&p| p) {
return None;
}
let full = |a: usize| spec.start[a] == 0 && spec.stop[a] == nodes[a] - 1;
if (0..3).any(|a| periodic[a] && !full(a)) {
return None;
}
let partial: Vec<usize> = (0..3).filter(|&a| !full(a)).collect();
let &[n] = partial.as_slice() else {
return None;
};
Some(CellPlaneWave {
axis: Axis::ALL[n],
start: spec.start[n],
stop: spec.stop[n],
})
}
pub(crate) fn wavevector(
fdtd: &Fdtd,
plane_waves: &[PlaneWaveSource],
nodes: [usize; 3],
) -> Result<Option<[f64; 3]>> {
if let Some(k) = fdtd.get_bloch_wavevector() {
return Ok(Some(k));
}
let periodic = periodic_axes(fdtd);
let mut found: Option<([f64; 3], &str)> = None;
for pw in plane_waves {
let Some(cell) = cell_geometry(&pw.spec, nodes, periodic) else {
continue;
};
let d = unit(pw.spec.direction);
let n = cell.axis.index();
let mut t = d;
t[n] = 0.0;
if t.iter().all(|x| x.abs() < 1e-12) {
continue;
}
if let Some(a) = (0..3).find(|&a| a != n && !periodic[a] && t[a].abs() >= 1e-12) {
return Err(Error::Invalid(format!(
"plane wave {:?}: oblique direction along {}, whose faces are not periodic",
pw.name,
Axis::ALL[a]
)));
}
let f = fdtd
.get_bloch_frequency()
.unwrap_or(pw.spec.match_frequency);
if !(f.is_finite() && f > 0.0) {
return Err(Error::Invalid(format!(
"plane wave {:?}: an oblique plane wave in a periodic cell needs a design \
frequency (Fdtd::bloch_frequency or a waveform with a centre frequency)",
pw.name
)));
}
let k0 = 2.0 * core::f64::consts::PI * f * (pw.spec.eps_r * pw.spec.mu_r).sqrt() / C0;
let k = t.map(|x| k0 * x);
if let Some((k1, other)) = found {
let scale = k1.iter().chain(&k).fold(0.0f64, |m, x| m.max(x.abs()));
if (0..3).any(|a| (k1[a] - k[a]).abs() > 1e-9 * scale) {
return Err(Error::Invalid(format!(
"plane waves {other:?} and {:?} need different Bloch wavevectors \
({k1:?} and {k:?} rad/m); one run has one",
pw.name
)));
}
} else {
found = Some((k, &pw.name));
}
}
Ok(found.map(|(k, _)| k))
}
fn unit(v: [f64; 3]) -> [f64; 3] {
let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
v.map(|x| x / l)
}
pub(crate) fn periodic_spec(
pw: &PlaneWaveSource,
fdtd: &Fdtd,
nodes: [usize; 3],
k: [f64; 3],
) -> core::result::Result<PeriodicPlaneWaveSpec, String> {
let Some(cell) = cell_geometry(&pw.spec, nodes, periodic_axes(fdtd)) else {
return Err(format!(
"plane wave {:?}: in a Bloch run a plane-wave box must span the periodic \
cell (whole extent of every periodic axis, and of all but one other axis); \
ignored",
pw.name
));
};
if !pw.spec.medium().is_lossless() {
return Err(format!(
"plane wave {:?}: lossy backgrounds (kappa = {}, sigma_m = {}) are not supported \
for periodic-cell plane waves; ignored",
pw.name, pw.spec.kappa, pw.spec.sigma_m
));
}
let n = cell.axis.index();
let d = unit(pw.spec.direction);
if d[n] == 0.0 {
return Err(format!(
"plane wave {:?}: direction parallel to the cell planes; ignored",
pw.name
));
}
if k[n] != 0.0 {
return Err(format!(
"plane wave {:?}: the Bloch wavevector has a component along the normal \
axis {}; ignored",
pw.name, cell.axis
));
}
let mut p = pw.spec.polarization;
p[n] = 0.0;
if p.iter().all(|&x| x == 0.0) {
return Err(format!(
"plane wave {:?}: excitation vector has no component tangential to the \
cell planes; ignored",
pw.name
));
}
Ok(
PeriodicPlaneWaveSpec::new(cell.axis, cell.start, cell.stop, d[n] > 0.0, k, p)
.with_background(pw.spec.eps_r, pw.spec.mu_r),
)
}
pub(crate) fn direction_mismatch(
pw: &PlaneWaveSource,
fdtd: &Fdtd,
spec: &PeriodicPlaneWaveSpec,
) -> Option<String> {
let f = fdtd
.get_bloch_frequency()
.unwrap_or(pw.spec.match_frequency);
let n = spec.axis.index();
let k0 = 2.0 * core::f64::consts::PI * f * (pw.spec.eps_r * pw.spec.mu_r).sqrt() / C0;
let mut own = unit(pw.spec.direction).map(|x| k0 * x);
own[n] = 0.0;
let scale = own
.iter()
.chain(&spec.k_t)
.fold(0.0f64, |m, x| m.max(x.abs()));
let agree =
!(f.is_finite() && f > 0.0) || (0..3).all(|a| (own[a] - spec.k_t[a]).abs() <= 1e-9 * scale);
(!agree).then(|| {
format!(
"plane wave {:?}: the explicit Bloch wavevector {:?} rad/m sets the incidence \
(its direction gives {own:?} rad/m at {f} Hz)",
pw.name, spec.k_t
)
})
}