use latx::Cell;
#[derive(Debug, Clone)]
pub struct RealSpaceGrid {
cell: Cell,
n: [usize; 3],
dv: f64,
}
impl RealSpaceGrid {
#[must_use]
pub fn new(cell: Cell, n: [usize; 3]) -> Self {
assert!(
n.iter().all(|&x| x > 0),
"grid dimensions must be > 0, got {n:?}"
);
let dv = cell.volume() / (n[0] * n[1] * n[2]) as f64;
Self { cell, n, dv }
}
#[must_use]
pub fn from_cutoff(cell: Cell, e_cut: f64) -> Self {
assert!(e_cut > 0.0, "density cutoff must be > 0, got {e_cut}");
let gmax = (2.0 * e_cut).sqrt();
let (b0, b1, b2) = cell.reciprocal().vectors();
let need = |b: [f64; 3]| {
let nb = (b[0] * b[0] + b[1] * b[1] + b[2] * b[2]).sqrt();
next_smooth(((2.0 * gmax / nb).ceil() as usize).max(1))
};
Self::new(cell, [need(b0), need(b1), need(b2)])
}
#[must_use]
pub fn cell(&self) -> &Cell {
&self.cell
}
#[must_use]
pub fn n(&self) -> [usize; 3] {
self.n
}
#[must_use]
pub fn n_points(&self) -> usize {
self.n[0] * self.n[1] * self.n[2]
}
#[must_use]
pub fn dv(&self) -> f64 {
self.dv
}
#[must_use]
pub fn linear_index(&self, idx: [usize; 3]) -> usize {
(idx[0] * self.n[1] + idx[1]) * self.n[2] + idx[2]
}
#[must_use]
pub fn point(&self, idx: [usize; 3]) -> [f64; 3] {
let f = [
idx[0] as f64 / self.n[0] as f64,
idx[1] as f64 / self.n[1] as f64,
idx[2] as f64 / self.n[2] as f64,
];
self.cell.frac_to_cart(f)
}
#[must_use]
pub fn points(&self) -> Vec<[f64; 3]> {
let mut pts = Vec::with_capacity(self.n_points());
for i in 0..self.n[0] {
for j in 0..self.n[1] {
for k in 0..self.n[2] {
pts.push(self.point([i, j, k]));
}
}
}
pts
}
pub(crate) fn g_vectors(&self) -> Vec<[f64; 3]> {
let recip = self.cell.reciprocal();
let mut g = Vec::with_capacity(self.n_points());
for i in 0..self.n[0] {
let fi = signed_freq(i, self.n[0]);
for j in 0..self.n[1] {
let fj = signed_freq(j, self.n[1]);
for k in 0..self.n[2] {
let fk = signed_freq(k, self.n[2]);
g.push(recip.g_vector([fi, fj, fk]));
}
}
}
g
}
}
fn signed_freq(i: usize, n: usize) -> i32 {
if i <= n / 2 {
i as i32
} else {
i as i32 - n as i32
}
}
fn next_smooth(mut n: usize) -> usize {
if n <= 1 {
return 1;
}
loop {
let mut m = n;
for p in [2usize, 3, 5, 7] {
while m % p == 0 {
m /= p;
}
}
if m == 1 {
return n;
}
n += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point_count_and_volume_element() {
let grid = RealSpaceGrid::new(Cell::cubic(4.0).unwrap(), [8, 8, 8]);
assert_eq!(grid.n_points(), 512);
assert!((grid.dv() * grid.n_points() as f64 - 64.0).abs() < 1e-10);
}
#[test]
fn grid_points_lie_in_cell() {
let grid = RealSpaceGrid::new(Cell::cubic(2.0).unwrap(), [4, 4, 4]);
assert_eq!(grid.point([0, 0, 0]), [0.0, 0.0, 0.0]);
let mid = grid.point([2, 2, 2]);
for c in mid {
assert!((c - 1.0).abs() < 1e-12);
}
}
#[test]
fn next_smooth_rounds_up() {
assert_eq!(next_smooth(1), 1);
assert_eq!(next_smooth(2), 2);
assert_eq!(next_smooth(11), 12); assert_eq!(next_smooth(13), 14); assert_eq!(next_smooth(625), 625); }
#[test]
fn from_cutoff_grows_with_cutoff() {
let cell = Cell::cubic(10.0).unwrap();
let coarse = RealSpaceGrid::from_cutoff(cell, 20.0);
let fine = RealSpaceGrid::from_cutoff(cell, 80.0);
assert!(fine.n()[0] >= coarse.n()[0]);
for &nd in &fine.n() {
assert_eq!(next_smooth(nd), nd);
}
}
#[test]
fn gamma_g_vector_is_zero() {
let grid = RealSpaceGrid::new(Cell::cubic(5.0).unwrap(), [4, 4, 4]);
let g = grid.g_vectors();
assert_eq!(g[0], [0.0, 0.0, 0.0]); }
}