use std::sync::{Arc, Mutex, PoisonError};
use super::Engine;
use super::grid::Grid;
use super::operator::{Hooks, Operator, OperatorHandle};
use super::slab::{SlabContext, SlabHooks, SlabMut, SlabPartition, SlabRef};
use crate::axis::Axis;
use crate::constants::{EPS0, MU0};
use crate::post::Complex64;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Pole {
Lorentz {
omega_p: f64,
omega_0: f64,
gamma: f64,
},
Drude {
omega_p: f64,
gamma: f64,
},
Debye {
delta_eps: f64,
tau: f64,
},
}
impl Pole {
pub const fn lorentz(omega_p: f64, omega_0: f64, gamma: f64) -> Self {
Pole::Lorentz {
omega_p,
omega_0,
gamma,
}
}
pub const fn drude(omega_p: f64, gamma: f64) -> Self {
Pole::Drude { omega_p, gamma }
}
pub const fn debye(delta_eps: f64, tau: f64) -> Self {
Pole::Debye { delta_eps, tau }
}
pub fn susceptibility(&self, omega: f64) -> Complex64 {
let j = Complex64::I;
match *self {
Pole::Lorentz {
omega_p,
omega_0,
gamma,
} => omega_p * omega_p / (omega_0 * omega_0 - omega * omega + j * (omega * gamma)),
Pole::Drude { omega_p, gamma } => {
-(omega_p * omega_p) / (omega * omega - j * (omega * gamma))
}
Pole::Debye { delta_eps, tau } => delta_eps / (1.0 + j * (omega * tau)),
}
}
pub fn discrete_susceptibility(&self, f: f64, dt: f64) -> Complex64 {
self.susceptibility(2.0 / dt * (core::f64::consts::PI * f * dt).tan())
}
pub fn scaled(self, s: f64) -> Self {
match self {
Pole::Lorentz {
omega_p,
omega_0,
gamma,
} => Pole::Lorentz {
omega_p: omega_p * s.sqrt(),
omega_0,
gamma,
},
Pole::Drude { omega_p, gamma } => Pole::Drude {
omega_p: omega_p * s.sqrt(),
gamma,
},
Pole::Debye { delta_eps, tau } => Pole::Debye {
delta_eps: delta_eps * s,
tau,
},
}
}
pub fn is_null(&self) -> bool {
match *self {
Pole::Lorentz { omega_p, .. } | Pole::Drude { omega_p, .. } => omega_p == 0.0,
Pole::Debye { delta_eps, .. } => delta_eps == 0.0,
}
}
pub fn validate(&self) -> Result<()> {
let vals: &[(&str, f64)] = match self {
Pole::Lorentz {
omega_p,
omega_0,
gamma,
} => &[("ω_p", *omega_p), ("ω_0", *omega_0), ("γ", *gamma)],
Pole::Drude { omega_p, gamma } => &[("ω_p", *omega_p), ("γ", *gamma)],
Pole::Debye { delta_eps, tau } => &[("Δε", *delta_eps), ("τ", *tau)],
};
for &(name, v) in vals {
if !(v.is_finite() && v >= 0.0) {
return Err(Error::Invalid(format!(
"dispersive pole {self:?}: {name} = {v} must be finite and >= 0"
)));
}
}
Ok(())
}
fn branch(&self, m: f64, dt: f64) -> (f64, f64, f64) {
let (l, r, inv_c) = match *self {
Pole::Lorentz {
omega_p,
omega_0,
gamma,
} => {
let l = 1.0 / (m * omega_p * omega_p);
(l, gamma * l, omega_0 * omega_0 * l)
}
Pole::Drude { omega_p, gamma } => {
let l = 1.0 / (m * omega_p * omega_p);
(l, gamma * l, 0.0)
}
Pole::Debye { delta_eps, tau } => {
let c = m * delta_eps;
(0.0, tau / c, 1.0 / c)
}
};
let a = 2.0 * l / dt;
(a, inv_c, a + r + 0.5 * dt * inv_c)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Field {
Electric,
Magnetic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EdgePole {
pub field: Field,
pub axis: Axis,
pub node: [usize; 3],
pub pole: Pole,
}
#[derive(Debug, Default, Clone)]
struct Side {
axis: Vec<u8>,
plane: Vec<u32>,
off: Vec<u32>,
prev: Vec<f64>,
sum_inv_z: Vec<f64>,
pole_end: Vec<u32>,
a: Vec<f64>,
inv_c: Vec<f64>,
inv_z: Vec<f64>,
j: Vec<f64>,
q: Vec<f64>,
}
impl Side {
fn edges(&self) -> usize {
self.axis.len()
}
fn capture(&mut self, f: &SlabRef<'_>) {
for e in 0..self.edges() {
let axis = Axis::ALL[self.axis[e] as usize];
self.prev[e] = f.plane(axis, self.plane[e] as usize)[self.off[e] as usize] as f64;
}
}
fn correct(&mut self, f: &mut SlabMut<'_>, coef: [&[f32]; 3], dt: f64) {
let p = f.nodes()[1] * f.nodes()[2];
let mut p0 = 0usize;
for e in 0..self.edges() {
let p1 = self.pole_end[e] as usize;
let ax = self.axis[e] as usize;
let gi = self.plane[e] as usize;
let off = self.off[e] as usize;
let cb = coef[ax][gi * p + off] as f64;
let half_prev = 0.5 * self.prev[e];
let plane = f.plane_mut(Axis::ALL[ax], gi);
let u_star = plane[off] as f64;
let mut s = 0.0;
for q in p0..p1 {
s +=
(self.a[q] * self.j[q] - self.q[q] * self.inv_c[q] + half_prev) * self.inv_z[q];
}
let u_new = ((u_star - cb * s) / (1.0 + 0.5 * cb * self.sum_inv_z[e])) as f32;
plane[off] = u_new;
let half_new = 0.5 * u_new as f64;
for q in p0..p1 {
let k = self.a[q] * self.j[q] - self.q[q] * self.inv_c[q] + half_prev;
let jb = (k + half_new) * self.inv_z[q];
self.j[q] = 2.0 * jb - self.j[q];
self.q[q] += dt * jb;
}
p0 = p1;
}
}
fn add_energy(&self, dt: f64, w: &mut f64) {
for q in 0..self.a.len() {
*w += 0.25 * self.a[q] * dt * self.j[q] * self.j[q]
+ 0.5 * self.inv_c[q] * self.q[q] * self.q[q];
}
}
}
#[derive(Debug, Default, Clone)]
struct SlabState {
e: Side,
h: Side,
}
#[derive(Debug, Clone)]
struct EdgeBranches {
field: Field,
axis: Axis,
node: [usize; 3],
poles: Vec<(f64, f64, f64)>,
}
#[derive(Debug)]
struct Parts {
dt: f64,
slabs: Vec<Mutex<SlabState>>,
}
impl Parts {
fn slab(&self, s: usize) -> std::sync::MutexGuard<'_, SlabState> {
self.slabs[s].lock().unwrap_or_else(PoisonError::into_inner)
}
}
impl SlabHooks for Parts {
fn post_h_slab(&self, i: &mut SlabMut<'_>, u: &SlabRef<'_>, ctx: &SlabContext<'_>) {
let mut st = self.slab(ctx.slab);
let co = ctx.coefficients;
if st.h.edges() > 0 {
st.h.correct(i, [co.db(Axis::X), co.db(Axis::Y), co.db(Axis::Z)], self.dt);
}
st.e.capture(u);
}
fn post_e_slab(&self, u: &mut SlabMut<'_>, i: &SlabRef<'_>, ctx: &SlabContext<'_>) {
let mut st = self.slab(ctx.slab);
let co = ctx.coefficients;
if st.e.edges() > 0 {
st.e.correct(u, [co.cb(Axis::X), co.cb(Axis::Y), co.cb(Axis::Z)], self.dt);
}
st.h.capture(i);
}
}
#[derive(Debug, Default)]
pub struct Dispersive {
entries: Vec<EdgePole>,
built: Vec<EdgeBranches>,
nodes: [usize; 3],
dt: f64,
skipped: usize,
installed: bool,
parts: Option<Arc<Parts>>,
}
impl Dispersive {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, field: Field, axis: Axis, node: [usize; 3], pole: Pole) {
if !pole.is_null() {
self.entries.push(EdgePole {
field,
axis,
node,
pole,
});
}
}
pub fn fill_cells(
&mut self,
grid: &Grid,
cells: [core::ops::Range<usize>; 3],
field: Field,
poles: &[Pole],
) -> Result<()> {
for p in poles {
p.validate()?;
}
let nc = grid.cells();
if (0..3).any(|a| cells[a].end > nc[a] || cells[a].start > cells[a].end) {
return Err(Error::Invalid(format!(
"dispersive cell box {cells:?} does not fit the grid ({nc:?} cells)"
)));
}
if cells.iter().any(|r| r.is_empty()) {
return Ok(());
}
let n = grid.nodes();
let inside = |a: usize, c: usize| cells[a].contains(&c);
for axis in Axis::ALL {
let (ab, ac) = axis.others();
let (a, b, c) = (axis.index(), ab.index(), ac.index());
let range = |t: usize, along: bool| {
if along {
cells[t].start..cells[t].end
} else {
cells[t].start..(cells[t].end + 1).min(n[t])
}
};
let (ra, rb, rc) = match field {
Field::Electric => (range(a, true), range(b, false), range(c, false)),
Field::Magnetic => (range(a, false), range(b, true), range(c, true)),
};
for pa in ra {
for pb in rb.clone() {
for pc in rc.clone() {
let mut node = [0; 3];
node[a] = pa;
node[b] = pb;
node[c] = pc;
let share = match field {
Field::Electric => {
let (mut w_in, mut w) = (0.0, 0.0);
for cb in [pb.wrapping_sub(1), pb] {
if cb >= n[b] - 1 {
continue;
}
for cc in [pc.wrapping_sub(1), pc] {
if cc >= n[c] - 1 {
continue;
}
let area = grid.delta(ab, cb) * grid.delta(ac, cc);
w += area;
if inside(b, cb) && inside(c, cc) {
w_in += area;
}
}
}
if w > 0.0 { w_in / w } else { 0.0 }
}
Field::Magnetic => {
let (mut w_in, mut w) = (0.0, 0.0);
for ca in [pa.wrapping_sub(1), pa] {
if ca >= n[a] - 1 {
continue;
}
let len = grid.delta(axis, ca);
w += len;
if inside(a, ca) {
w_in += len;
}
}
if w > 0.0 { w_in / w } else { 0.0 }
}
};
if share > 0.0 {
for &p in poles {
self.push(field, axis, node, p.scaled(share));
}
}
}
}
}
}
Ok(())
}
pub fn push_entry(&mut self, e: EdgePole) {
self.push(e.field, e.axis, e.node, e.pole);
}
pub fn entries(&self) -> &[EdgePole] {
&self.entries
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn edges(&self) -> usize {
match &self.parts {
Some(p) => (0..p.slabs.len())
.map(|s| {
let st = p.slab(s);
st.e.edges() + st.h.edges()
})
.sum(),
None => self.built.len(),
}
}
pub fn skipped(&self) -> usize {
self.skipped
}
fn install(&mut self, engine: &Engine) -> Result<()> {
if self.installed {
return Err(Error::Invalid("dispersive medium already installed".into()));
}
let grid = engine.grid();
let n = grid.nodes();
let dt = engine.dt();
for e in &self.entries {
e.pole.validate()?;
let a = e.axis.index();
let (b, c) = e.axis.others();
let (b, c) = (b.index(), c.index());
let exists = (0..3).all(|t| e.node[t] < n[t])
&& match e.field {
Field::Electric => e.node[a] + 1 < n[a],
Field::Magnetic => e.node[b] + 1 < n[b] && e.node[c] + 1 < n[c],
};
if !exists {
return Err(Error::Invalid(format!(
"dispersive {:?} edge {}:{:?} is not in the grid ({n:?} nodes)",
e.field, e.axis, e.node
)));
}
}
let mut entries = self.entries.clone();
let key = |e: &EdgePole| (e.node[0], e.field, e.axis.index(), e.node[1], e.node[2]);
entries.sort_by_key(key);
let co = engine.coefficients();
let mut built: Vec<EdgeBranches> = Vec::new();
let mut skipped = 0;
let mut last = None;
for e in &entries {
let k = key(e);
let idx = grid.index(e.node);
let (ab, ac) = e.axis.others();
let (coef, m) = match e.field {
Field::Electric => (
co.cb(e.axis)[idx],
EPS0 * grid.dual_delta(ab, e.node[ab.index()])
* grid.dual_delta(ac, e.node[ac.index()])
/ grid.delta(e.axis, e.node[e.axis.index()]),
),
Field::Magnetic => (
co.db(e.axis)[idx],
MU0 * grid.delta(ab, e.node[ab.index()]) * grid.delta(ac, e.node[ac.index()])
/ grid.dual_delta(e.axis, e.node[e.axis.index()]),
),
};
let new_edge = last != Some(k);
last = Some(k);
if coef == 0.0 {
if new_edge {
skipped += 1;
}
continue;
}
if new_edge {
built.push(EdgeBranches {
field: e.field,
axis: e.axis,
node: e.node,
poles: Vec::new(),
});
}
let (a, inv_c, z) = e.pole.branch(m, dt);
built
.last_mut()
.expect("edge just pushed")
.poles
.push((a, inv_c, 1.0 / z));
}
drop(co);
self.built = built;
self.skipped = skipped;
self.nodes = n;
self.dt = dt;
self.installed = true;
Ok(())
}
pub fn add_to(mut self, engine: &mut Engine) -> Result<OperatorHandle<Dispersive>> {
self.install(engine)?;
Ok(engine.add_operator(self))
}
fn sum_energy(&self, field: Field) -> f64 {
let Some(p) = &self.parts else { return 0.0 };
let mut w = 0.0;
for s in 0..p.slabs.len() {
let st = p.slab(s);
match field {
Field::Electric => st.e.add_energy(p.dt, &mut w),
Field::Magnetic => st.h.add_energy(p.dt, &mut w),
}
}
w
}
pub fn electric_energy(&self) -> f64 {
self.sum_energy(Field::Electric)
}
pub fn magnetic_energy(&self) -> f64 {
self.sum_energy(Field::Magnetic)
}
}
impl Operator for Dispersive {
fn hooks(&self) -> Hooks {
Hooks::NONE
}
fn slab_hooks(&mut self, part: &SlabPartition<'_>) -> Option<Arc<dyn SlabHooks>> {
if !self.installed || self.parts.is_some() {
return None;
}
assert_eq!(
part.grid().nodes(),
self.nodes,
"dispersive medium built for another grid"
);
assert_eq!(part.dt(), self.dt, "dispersive medium built for another dt");
let nz = self.nodes[2];
let mut slabs: Vec<SlabState> = vec![SlabState::default(); part.slabs()];
let mut s = 0;
for eb in std::mem::take(&mut self.built) {
while !part.planes(s).contains(&eb.node[0]) {
s += 1;
}
let side = match eb.field {
Field::Electric => &mut slabs[s].e,
Field::Magnetic => &mut slabs[s].h,
};
side.axis.push(eb.axis.index() as u8);
side.plane.push(eb.node[0] as u32);
side.off.push((eb.node[1] * nz + eb.node[2]) as u32);
side.prev.push(0.0);
let mut sum = 0.0;
for (a, inv_c, inv_z) in eb.poles {
side.a.push(a);
side.inv_c.push(inv_c);
side.inv_z.push(inv_z);
side.j.push(0.0);
side.q.push(0.0);
sum += inv_z;
}
side.sum_inv_z.push(sum);
side.pole_end.push(side.a.len() as u32);
}
let parts = Arc::new(Parts {
dt: self.dt,
slabs: slabs.into_iter().map(Mutex::new).collect(),
});
self.parts = Some(parts.clone());
Some(parts)
}
fn energy(&self) -> f64 {
self.electric_energy() + self.magnetic_energy()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn susceptibility_forms() {
let w = 2e10;
let d = Pole::drude(3e10, 1e9).susceptibility(w);
let l = Pole::lorentz(3e10, 0.0, 1e9).susceptibility(w);
assert!((d - l).norm() < 1e-12 * d.norm());
assert!(d.im < 0.0);
let db = Pole::debye(2.0, 1e-11).susceptibility(w);
assert!(
db.im < 0.0 && (Pole::debye(2.0, 1e-11).susceptibility(0.0).re - 2.0).abs() < 1e-15
);
assert!(Pole::lorentz(1.0, f64::NAN, 0.0).validate().is_err());
assert!(Pole::debye(-1.0, 0.0).validate().is_err());
let s = Pole::lorentz(3e10, 1e10, 1e9).scaled(0.25);
let r = s.susceptibility(w) / Pole::lorentz(3e10, 1e10, 1e9).susceptibility(w);
assert!((r - 0.25).norm() < 1e-12);
}
#[test]
fn branch_admittance_matches_susceptibility() {
let dt = 1e-12;
let m = EPS0 * 2.5e-3;
let w = 3e10;
let j = Complex64::I;
for p in [
Pole::lorentz(4e10, 2e10, 3e9),
Pole::drude(4e10, 3e9),
Pole::debye(3.0, 2e-11),
] {
let (a, inv_c, z) = p.branch(m, dt);
let l = 0.5 * a * dt;
let r = z - a - 0.5 * dt * inv_c;
let zb = j * (w * l) + r + inv_c / (j * w);
let y = 1.0 / zb;
let want = j * (w * EPS0 * 2.5e-3) * p.susceptibility(w);
assert!((y - want).norm() < 1e-9 * want.norm(), "{p:?}");
}
}
}