use crate::axis::Axis;
use crate::engine::coeffs::{Coefficients, FaceCondition, FaceConditions};
use crate::engine::fields::Fields;
use crate::engine::run::{Stepper, drive};
use crate::engine::{Engine, EngineBuilder, Grid, Progress, RunLimits, RunOutcome};
use crate::fdtd::Face;
use crate::{Error, Result};
fn pair_weights(wl: f64, wh: f64) -> Option<(f64, f64)> {
match (wl.is_infinite(), wh.is_infinite()) {
(true, true) => Some((1.0, 1.0)),
(false, false) => Some((wl, wh)),
_ => None,
}
}
#[derive(Debug, Clone)]
pub(crate) struct Periodic {
axes: [bool; 3],
n: [usize; 3],
}
impl Periodic {
pub(crate) fn from_faces(faces: &FaceConditions, n: [usize; 3]) -> Result<Option<Self>> {
let mut axes = [false; 3];
for a in Axis::ALL {
let lo = faces[Face::new(a, false)] == FaceCondition::Periodic;
let hi = faces[Face::new(a, true)] == FaceCondition::Periodic;
if lo != hi {
return Err(Error::Invalid(format!(
"periodic boundary on {} needs {} periodic too",
Face::new(a, !lo),
Face::new(a, lo)
)));
}
axes[a.index()] = lo;
}
Ok(axes.iter().any(|&p| p).then_some(Self { axes, n }))
}
fn index(&self, [i, j, k]: [usize; 3]) -> usize {
(i * self.n[1] + j) * self.n[2] + k
}
fn weight(&self, cb: &[f32], c: usize, a: usize, p: [usize; 3]) -> f64 {
let w = |p: [usize; 3]| {
let b = cb[self.index(p)];
if b == 0.0 {
f64::INFINITY
} else {
1.0 / b as f64
}
};
let mut sum = w(p);
for b in (0..a).filter(|&b| b != c && self.axes[b]) {
let last = self.n[b] - 1;
if p[b] == 0 || p[b] == last {
let mut q = p;
q[b] = last - p[b];
sum += w(q);
}
}
sum
}
fn pairs(
&self,
co: &Coefficients,
mut pair: impl FnMut(usize, Axis, [usize; 3], [usize; 3], f64, f64),
) {
let n = self.n;
for a in (0..3).filter(|&a| self.axes[a]) {
let (b, c) = match a {
0 => (1, 2),
1 => (0, 2),
_ => (0, 1),
};
for comp in [b, c] {
let axis = Axis::ALL[comp];
let cb = co.cb(axis);
let other = if comp == b { c } else { b };
for x in 0..n[comp] - 1 {
for y in 0..n[other] {
let mut lo = [0; 3];
lo[comp] = x;
lo[other] = y;
let mut hi = lo;
hi[a] = n[a] - 1;
let (wl, wh) = (self.weight(cb, comp, a, lo), self.weight(cb, comp, a, hi));
pair(a, axis, lo, hi, wl, wh);
}
}
}
}
}
pub(crate) fn merge(&self, f: &mut Fields<'_>, co: &Coefficients) {
self.pairs(co, |_, axis, lo, hi, wl, wh| {
let v = match pair_weights(wl, wh) {
None => 0.0,
Some((wl, wh)) => {
let (ul, uh) = (f.u.get(axis, lo) as f64, f.u.get(axis, hi) as f64);
((wl * ul + wh * uh) / (wl + wh)) as f32
}
};
f.u.set(axis, lo, v);
f.u.set(axis, hi, v);
});
}
pub(crate) fn merge_f64(&self, u: &mut [Vec<f64>; 3], co: &Coefficients) {
self.pairs(co, |_, axis, lo, hi, wl, wh| {
let a = axis.index();
let (il, ih) = (self.index(lo), self.index(hi));
let v = match pair_weights(wl, wh) {
None => 0.0,
Some((wl, wh)) => (wl * u[a][il] + wh * u[a][ih]) / (wl + wh),
};
u[a][il] = v;
u[a][ih] = v;
});
}
fn merge_bloch(
&self,
re: &mut Fields<'_>,
im: &mut Fields<'_>,
co: &Coefficients,
phase: &[(f64, f64); 3],
) {
self.pairs(co, |a, axis, lo, hi, wl, wh| {
let (c, s) = phase[a];
let (vr, vi) = if let Some((wl, wh)) = pair_weights(wl, wh) {
let (lr, li) = (re.u.get(axis, lo) as f64, im.u.get(axis, lo) as f64);
let (hr, hi) = (re.u.get(axis, hi) as f64, im.u.get(axis, hi) as f64);
let w = wl + wh;
(
(wl * lr + wh * (hr * c - hi * s)) / w,
(wl * li + wh * (hr * s + hi * c)) / w,
)
} else {
(0.0, 0.0)
};
re.u.set(axis, lo, vr as f32);
im.u.set(axis, lo, vi as f32);
re.u.set(axis, hi, (vr * c + vi * s) as f32);
im.u.set(axis, hi, (vi * c - vr * s) as f32);
});
}
}
pub struct BlochPair {
re: Engine,
im: Engine,
coupling: BlochCoupling,
}
impl core::fmt::Debug for BlochPair {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("BlochPair")
.field("phase", &self.coupling.phase)
.field("re", &self.re)
.field("im", &self.im)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
pub(crate) struct BlochCoupling {
periodic: Periodic,
phase: [f64; 3],
trig: [(f64, f64); 3],
}
impl BlochCoupling {
pub(crate) fn new(builder: EngineBuilder, phase: [f64; 3]) -> Result<(Self, EngineBuilder)> {
let periodic =
Periodic::from_faces(&builder.faces, builder.grid.nodes())?.ok_or_else(|| {
Error::Invalid("Bloch pair: no face pair is FaceCondition::Periodic".into())
})?;
for a in Axis::ALL {
let p = phase[a.index()];
if !p.is_finite() || (p != 0.0 && !periodic.axes[a.index()]) {
return Err(Error::Invalid(format!(
"Bloch phase {p} on axis {a} (must be finite, and 0 unless the axis is periodic)"
)));
}
}
let mut b = builder;
for face in Face::ALL {
if b.faces[face] == FaceCondition::Periodic {
b.faces[face] = FaceCondition::Pmc;
}
}
let coupling = Self {
periodic,
phase,
trig: phase.map(|p| (p.cos(), p.sin())),
};
Ok((coupling, b))
}
pub(crate) fn phase(&self) -> [f64; 3] {
self.phase
}
fn merge(&self, re: &Engine, im: &Engine) {
let co = re.coefficients();
let mut fr = re.shared.store.lock();
let mut fi = im.shared.store.lock();
self.periodic.merge_bloch(&mut fr, &mut fi, &co, &self.trig);
}
pub(crate) fn step(&self, re: &mut Engine, im: &mut Engine, energy: bool) -> f64 {
let w = if energy {
re.step_with_energy() + im.step_with_energy()
} else {
re.step();
im.step();
0.0
};
self.merge(re, im);
w
}
}
pub(crate) struct BlochStepper<'a> {
pub(crate) re: &'a mut Engine,
pub(crate) im: &'a mut Engine,
pub(crate) coupling: &'a BlochCoupling,
}
impl Stepper for BlochStepper<'_> {
fn timestep(&self) -> u64 {
self.re.timestep()
}
fn dt(&self) -> f64 {
self.re.dt()
}
fn time(&self) -> f64 {
self.re.time()
}
fn cells(&self) -> f64 {
2.0 * self.re.grid().num_cells() as f64
}
fn step_with_energy(&mut self) -> f64 {
self.coupling.step(self.re, self.im, true)
}
fn run(&mut self, n: u64) {
for _ in 0..n {
self.coupling.step(self.re, self.im, false);
}
}
}
impl BlochPair {
pub fn new(builder: EngineBuilder, phase: [f64; 3]) -> Result<Self> {
let (coupling, b) = BlochCoupling::new(builder, phase)?;
let re = b.clone().build()?;
let im = b.build()?;
Ok(Self { re, im, coupling })
}
pub fn phase_for(grid: &Grid, k: [f64; 3]) -> [f64; 3] {
Axis::ALL.map(|a| {
let l = grid.lines(a);
k[a.index()] * (l[l.len() - 1] - l[0])
})
}
pub fn phase(&self) -> [f64; 3] {
self.coupling.phase
}
pub fn real(&self) -> &Engine {
&self.re
}
pub fn real_mut(&mut self) -> &mut Engine {
&mut self.re
}
pub fn imag(&self) -> &Engine {
&self.im
}
pub fn imag_mut(&mut self) -> &mut Engine {
&mut self.im
}
pub fn engines_mut(&mut self) -> (&mut Engine, &mut Engine) {
(&mut self.re, &mut self.im)
}
pub fn step(&mut self) {
self.coupling.step(&mut self.re, &mut self.im, false);
}
pub fn step_with_energy(&mut self) -> f64 {
self.coupling.step(&mut self.re, &mut self.im, true)
}
pub fn run(&mut self, n: u64) {
for _ in 0..n {
self.step();
}
}
pub fn run_until(&mut self, limits: &RunLimits) -> Result<RunOutcome> {
self.run_until_with(limits, |_| core::ops::ControlFlow::Continue(()))
}
pub fn run_until_with<F>(&mut self, limits: &RunLimits, progress: F) -> Result<RunOutcome>
where
F: FnMut(&Progress) -> core::ops::ControlFlow<()>,
{
let mut s = BlochStepper {
re: &mut self.re,
im: &mut self.im,
coupling: &self.coupling,
};
drive(&mut s, limits, progress)
}
pub fn energy(&self) -> f64 {
self.re.energy() + self.im.energy()
}
}