use super::super::{init::domain::Domain, solver::PoissonSolver, types::*};
use rayon::prelude::*;
use rustfft::{FftPlanner, num_complex::Complex};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
pub struct VgfPoisson {
shape: [usize; 3],
dx: [f64; 3],
green_hat: Vec<Complex<f64>>,
fwd: [Arc<dyn rustfft::Fft<f64>>; 3],
inv: [Arc<dyn rustfft::Fft<f64>>; 3],
scratch_cache: Mutex<Vec<Complex<f64>>>,
progress: Option<Arc<super::super::progress::StepProgress>>,
}
impl VgfPoisson {
pub fn new(domain: &Domain) -> Self {
use std::f64::consts::PI;
let shape = [
domain.spatial_res.x1 as usize,
domain.spatial_res.x2 as usize,
domain.spatial_res.x3 as usize,
];
let dx = domain.dx();
let [nx, ny, nz] = shape;
let [nx2, ny2, nz2] = [2 * nx, 2 * ny, 2 * nz];
let n2_total = nx2 * ny2 * nz2;
let lx = nx as f64 * dx[0];
let ly = ny as f64 * dx[1];
let lz = nz as f64 * dx[2];
let l_max = lx.max(ly).max(lz);
let r = 3.0_f64.sqrt() * l_max;
let mut planner = FftPlanner::new();
let fwd = [
planner.plan_fft_forward(nx2),
planner.plan_fft_forward(ny2),
planner.plan_fft_forward(nz2),
];
let inv = [
planner.plan_fft_inverse(nx2),
planner.plan_fft_inverse(ny2),
planner.plan_fft_inverse(nz2),
];
let mut green_hat = vec![Complex::new(0.0, 0.0); n2_total];
green_hat
.par_chunks_mut(nz2)
.enumerate()
.for_each(|(row, chunk)| {
let ix = row / ny2;
let iy = row % ny2;
let kx = wavenumber(ix, nx2, dx[0]);
let ky = wavenumber(iy, ny2, dx[1]);
for (iz, c) in chunk.iter_mut().enumerate() {
let kz = wavenumber(iz, nz2, dx[2]);
let k2 = kx * kx + ky * ky + kz * kz;
if k2 < 1e-30 {
*c = Complex::new(-r * r / (4.0 * PI), 0.0);
} else {
let k = k2.sqrt();
*c = Complex::new(-1.0 / (2.0 * PI * k2) * (1.0 - (k * r).cos()), 0.0);
}
}
});
Self {
shape,
dx,
green_hat,
fwd,
inv,
scratch_cache: Mutex::new(Vec::new()),
progress: None,
}
}
}
#[inline]
fn wavenumber(i: usize, n: usize, cell_size: f64) -> f64 {
use std::f64::consts::PI;
let j = if i < n / 2 {
i as i64
} else {
i as i64 - n as i64
};
2.0 * PI * j as f64 / (n as f64 * cell_size)
}
impl PoissonSolver for VgfPoisson {
fn set_progress(&mut self, p: Arc<super::super::progress::StepProgress>) {
self.progress = Some(p);
}
fn solve(&self, density: &DensityField, g: f64) -> PotentialField {
let _span = tracing::info_span!("vgf_poisson_solve").entered();
use std::f64::consts::PI;
let [nx, ny, nz] = self.shape;
let [nx2, ny2, nz2] = [2 * nx, 2 * ny, 2 * nz];
let n2_total = nx2 * ny2 * nz2;
let mut rho_pad = vec![Complex::new(0.0, 0.0); n2_total];
rho_pad
.par_chunks_mut(nz2)
.enumerate()
.for_each(|(row, chunk)| {
let ix = row / ny2;
let iy = row % ny2;
if ix < nx && iy < ny {
for (iz, c) in chunk.iter_mut().take(nz).enumerate() {
*c = Complex::new(density.data[ix * ny * nz + iy * nz + iz], 0.0);
}
}
});
let mut scratch =
std::mem::take(&mut *self.scratch_cache.lock().unwrap_or_else(|e| e.into_inner()));
scratch.resize(n2_total, Complex::new(0.0, 0.0));
super::fft_utils::fft_3d_c2c_scratch(
&mut rho_pad,
&mut scratch,
[nx2, ny2, nz2],
&self.fwd,
);
let dx3 = self.dx[0] * self.dx[1] * self.dx[2];
let factor = 4.0 * PI * g * dx3;
let total = n2_total as u64;
let counter = AtomicU64::new(0);
let report_interval = (total / 100).max(1);
rho_pad
.par_iter_mut()
.zip(self.green_hat.par_iter())
.for_each(|(rho, green)| {
*rho = factor * green * *rho;
if let Some(ref p) = self.progress {
let c = counter.fetch_add(1, Ordering::Relaxed);
if c.is_multiple_of(report_interval) {
p.set_intra_progress(c, total);
}
}
});
super::fft_utils::fft_3d_c2c_scratch(
&mut rho_pad,
&mut scratch,
[nx2, ny2, nz2],
&self.inv,
);
*self.scratch_cache.lock().unwrap_or_else(|e| e.into_inner()) = scratch;
let norm = n2_total as f64;
let mut phi = vec![0.0f64; nx * ny * nz];
phi.par_chunks_mut(nz).enumerate().for_each(|(row, chunk)| {
let ix = row / ny;
let iy = row % ny;
let base = ix * ny2 * nz2 + iy * nz2;
for iz in 0..nz {
chunk[iz] = rho_pad[base + iz].re / norm;
}
});
PotentialField {
data: phi,
shape: density.shape,
}
}
fn compute_acceleration(&self, potential: &PotentialField) -> AccelerationField {
super::utils::finite_difference_acceleration(potential, &self.dx)
}
}