use crate::atoms::Lattice;
use crate::utils::{cross, invert_lattice, vdot};
pub struct Voronoi {
pub vectors: Vec<Vec<usize>>,
pub alphas: Vec<f64>,
pub volume: f64,
}
impl Voronoi {
pub fn new(lattice: &Lattice) -> Self {
let (vectors, alphas, volume) = Voronoi::voronoi_vectors(lattice);
Self {
vectors,
alphas,
volume,
}
}
fn voronoi_vectors(lattice: &Lattice) -> (Vec<Vec<usize>>, Vec<f64>, f64) {
let mut vectors = Vec::<Vec<usize>>::with_capacity(14);
let mut alphas = Vec::<f64>::with_capacity(14);
let volume = lattice
.reduced_cartesian_shift_matrix
.iter()
.take(13) .enumerate()
.filter_map(|(vec_i, n)| {
let n_mag = vdot(*n, *n) * 0.5;
let mut vertices = lattice
.reduced_cartesian_shift_matrix
.iter()
.enumerate()
.filter_map(|(neigh_i, r1)| {
let r1_mag = vdot(*r1, *r1) * 0.5;
let vertices = lattice
.reduced_cartesian_shift_matrix
.iter()
.skip(neigh_i + 1)
.filter_map(|r2| {
let r2_mag = vdot(*r2, *r2) * 0.5;
if let Some(vector_inv) =
invert_lattice(&[*n, *r1, *r2])
{
let mut vertex = [0f64; 3];
for i in 0..3 {
vertex[i] = vdot(
[n_mag, r1_mag, r2_mag],
vector_inv[i],
)
}
let vertex_mag = vdot(vertex, vertex);
if (vertex_mag - 0.5 * n_mag).abs()
< f64::EPSILON
{
return None;
}
for s in lattice
.reduced_cartesian_shift_matrix
.iter()
{
let ss2 = 0.5 * vdot(*s, *s);
if vdot(vertex, *s) > ss2 + f64::EPSILON
{
return None;
}
}
Some(vertex)
} else {
None
}
})
.collect::<Vec<[f64; 3]>>();
if vertices.is_empty() {
None
} else {
Some(vertices)
}
})
.flatten()
.collect::<Vec<[f64; 3]>>();
if vertices.is_empty() {
return None;
}
let mut rx = vertices[0];
let r_coeff = vdot(rx, *n) / vdot(*n, *n);
for (element, n) in rx.iter_mut().zip(n) {
*element -= n * r_coeff;
}
let r_coeff = vdot(rx, rx).powf(-0.5);
for element in rx.iter_mut() {
*element *= r_coeff;
}
let mut ry = cross(*n, rx);
let r_coeff = vdot(ry, ry).powf(-0.5);
for element in ry.iter_mut() {
*element *= r_coeff;
}
vertices.sort_unstable_by({
|a, b| {
let c = vdot(*a, ry).atan2(vdot(*a, rx));
let d = vdot(*b, ry).atan2(vdot(*b, rx));
c.partial_cmp(&d).unwrap()
}
});
vertices.push(vertices[0]);
let wedge_volume = vertices
.windows(2)
.fold(0f64, |sum, w| sum + vdot(w[0], cross(w[1], *n)))
/ 12f64;
if wedge_volume.abs() < f64::EPSILON {
return None;
}
let alpha = 6f64 * wedge_volume / vdot(*n, *n);
vectors.push(lattice.reduced_grid_shift_matrix[vec_i].clone());
alphas.push(alpha);
vectors.push(
lattice.reduced_grid_shift_matrix[26 - vec_i].clone(),
);
alphas.push(alpha);
Some(wedge_volume * 2f64)
})
.sum();
vectors.shrink_to_fit();
alphas.shrink_to_fit();
(vectors, alphas, volume)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::atoms::Lattice;
#[test]
fn test_voronoi_cubic() {
let matrix = [[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]];
let lattice = Lattice::new(matrix);
let voronoi = Voronoi::new(&lattice);
assert!((voronoi.volume - 1000.0).abs() < 1e-6, "Volume mismatch");
assert!(!voronoi.vectors.is_empty());
assert_eq!(voronoi.vectors.len(), voronoi.alphas.len());
for &alpha in &voronoi.alphas {
assert!(
(alpha - 10.0).abs() < 1e-6,
"Expected alpha 10.0, got {}",
alpha
);
}
}
#[test]
fn test_voronoi_orthorhombic() {
let matrix = [[2.0, 0.0, 0.0], [0.0, 3.0, 0.0], [0.0, 0.0, 4.0]];
let lattice = Lattice::new(matrix);
let voronoi = Voronoi::new(&lattice);
assert!((voronoi.volume - 24.0).abs() < 1e-6);
let mut found_6 = false;
let mut found_2_6 = false;
let mut found_1_5 = false;
for &alpha in &voronoi.alphas {
if (alpha - 6.0).abs() < 1e-4 {
found_6 = true;
}
if (alpha - (8.0 / 3.0)).abs() < 1e-4 {
found_2_6 = true;
}
if (alpha - 1.5).abs() < 1e-4 {
found_1_5 = true;
}
}
assert!(
found_6 && found_2_6 && found_1_5,
"Missing expected face weights"
);
}
#[test]
fn test_voronoi_vectors() {
let lattice =
Lattice::new([[1.0, 0., 0.], [0.707, 0.707, 0.], [0., 0., 1.]]);
let voronoi = Voronoi::new(&lattice);
let vecs = vec![
vec![4],
vec![22],
vec![7],
vec![19],
vec![10],
vec![16],
vec![12],
vec![14],
];
assert_eq!(vecs, voronoi.vectors)
}
}