use core::f64::consts::TAU;
use super::fields::Fields;
use super::grid::Grid;
use super::operator::{Operator, StepContext};
use crate::axis::Axis;
use crate::post::Complex64;
use crate::post::dft::phase_cycles;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CellBox {
lo: [usize; 3],
hi: [usize; 3],
}
impl CellBox {
pub fn new(a: [usize; 3], b: [usize; 3]) -> Self {
Self {
lo: [0, 1, 2].map(|i| a[i].min(b[i])),
hi: [0, 1, 2].map(|i| a[i].max(b[i])),
}
}
pub fn whole(grid: &Grid) -> Self {
Self::new([0; 3], grid.nodes().map(|n| n - 1))
}
pub fn fitted(mut self, nodes: [usize; 3]) -> Result<Self> {
for a in 0..3 {
if nodes[a] < 2 || self.hi[a] >= nodes[a] {
return Err(Error::Invalid(format!(
"cell box {:?}..{:?} does not fit a grid of {nodes:?} nodes",
self.lo, self.hi
)));
}
if self.lo[a] == self.hi[a] {
if self.hi[a] + 1 < nodes[a] {
self.hi[a] += 1;
} else {
self.lo[a] -= 1;
}
}
}
Ok(self)
}
pub fn lo(&self) -> [usize; 3] {
self.lo
}
pub fn hi(&self) -> [usize; 3] {
self.hi
}
pub fn cells(&self) -> [usize; 3] {
[0, 1, 2].map(|a| self.hi[a] - self.lo[a])
}
pub fn num_cells(&self) -> usize {
self.cells().iter().product()
}
pub fn lines<'g>(&self, grid: &'g Grid, axis: Axis) -> &'g [f64] {
let a = axis.index();
&grid.lines(axis)[self.lo[a]..=self.hi[a]]
}
}
#[derive(Debug, Clone)]
pub struct EdgeDft {
cells: CellBox,
freqs: Vec<f64>,
interval: u64,
acc: [Vec<Complex64>; 3],
samples: u64,
dt: f64,
started: bool,
phasors: Vec<Complex64>,
}
impl EdgeDft {
pub fn new(cells: CellBox, freqs: impl Into<Vec<f64>>) -> Self {
let freqs = freqs.into();
let acc = Axis::ALL.map(|a| {
vec![Complex64::ZERO; Self::dims_of(&cells, a).iter().product::<usize>() * freqs.len()]
});
Self {
cells,
phasors: vec![Complex64::ZERO; freqs.len()],
freqs,
interval: 1,
acc,
samples: 0,
dt: 0.0,
started: false,
}
}
pub fn interval(mut self, k: u64) -> Self {
self.interval = k.max(1);
self
}
fn dims_of(cells: &CellBox, axis: Axis) -> [usize; 3] {
let c = cells.cells();
[0, 1, 2].map(|b| if b == axis.index() { c[b] } else { c[b] + 1 })
}
pub fn cell_box(&self) -> &CellBox {
&self.cells
}
pub fn edge_dims(&self, axis: Axis) -> [usize; 3] {
Self::dims_of(&self.cells, axis)
}
pub fn freqs(&self) -> &[f64] {
&self.freqs
}
pub fn samples(&self) -> u64 {
self.samples
}
pub fn dt(&self) -> f64 {
self.dt
}
pub fn sample_interval(&self) -> f64 {
self.interval as f64 * self.dt
}
pub fn voltages(&self, axis: Axis, k: usize) -> Vec<Complex64> {
assert!(k < self.freqs.len(), "frequency index {k} out of range");
let n: usize = self.edge_dims(axis).iter().product();
let s = self.sample_interval();
self.acc[axis.index()][k * n..(k + 1) * n]
.iter()
.map(|&c| c * s)
.collect()
}
pub fn reset(&mut self) {
for a in &mut self.acc {
a.fill(Complex64::ZERO);
}
self.samples = 0;
}
fn sample(&mut self, fields: &Fields<'_>, m: u64, dt: f64) {
self.dt = dt;
if !m.is_multiple_of(self.interval) {
return;
}
for (p, &f) in self.phasors.iter_mut().zip(&self.freqs) {
*p = Complex64::cis(-TAU * phase_cycles(f, 0.0, dt, m as f64));
}
let lo = self.cells.lo;
for axis in Axis::ALL {
let d = Self::dims_of(&self.cells, axis);
let n: usize = d.iter().product();
let acc = &mut self.acc[axis.index()];
let mut idx = 0;
for i in 0..d[0] {
for j in 0..d[1] {
for k in 0..d[2] {
let u = f64::from(fields.u.get(axis, [lo[0] + i, lo[1] + j, lo[2] + k]));
if u != 0.0 {
for (f, &p) in self.phasors.iter().enumerate() {
acc[f * n + idx] += p * u;
}
}
idx += 1;
}
}
}
}
self.samples += 1;
}
}
impl Operator for EdgeDft {
fn pre_h(&mut self, fields: &mut Fields<'_>, ctx: &StepContext<'_>) {
if !core::mem::replace(&mut self.started, true) {
self.sample(fields, ctx.step, ctx.dt);
}
}
fn post_e(&mut self, fields: &mut Fields<'_>, ctx: &StepContext<'_>) {
self.started = true;
self.sample(fields, ctx.step + 1, ctx.dt);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::Engine;
#[test]
fn cell_box_fitting() {
let b = CellBox::new([3, 1, 4], [1, 1, 4])
.fitted([5, 5, 5])
.unwrap();
assert_eq!(b.lo(), [1, 1, 3]);
assert_eq!(b.hi(), [3, 2, 4]);
assert_eq!(b.cells(), [2, 1, 1]);
assert!(CellBox::new([0; 3], [5; 3]).fitted([5, 5, 5]).is_err());
}
#[test]
fn static_field_transform() {
let grid = Grid::uniform([4, 4, 4], [1e-3; 3]).unwrap();
let mut e = Engine::builder(grid).threads(1).build().unwrap();
let h = e.add_operator(EdgeDft::new(CellBox::new([1, 1, 1], [2, 3, 3]), [0.0]));
let dft = e.operator(&h);
assert_eq!(dft.edge_dims(Axis::X), [1, 3, 3]);
assert_eq!(dft.edge_dims(Axis::Y), [2, 2, 3]);
e.run(5);
let dft = e.operator(&h);
assert_eq!(dft.samples(), 6);
assert!(
dft.voltages(Axis::Z, 0)
.iter()
.all(|v| *v == Complex64::ZERO)
);
}
}