use crate::cell::AperiodicAxis;
use crate::debug;
use crate::mathfunc::{
Mat3, Mat3I, Vec3, mat_cast_matrix_3d_to_3i,
mat_get_determinant_d3, mat_get_determinant_i3, mat_inverse_matrix_d3, mat_multiply_matrix_d3,
mat_norm_squared_d3,
};
use std::env;
const ZERO_PREC: f64 = 1e-10;
fn get_num_attempts() -> i32 {
if let Ok(val_str) = env::var("SPGLIB_NUM_ATTEMPTS") {
if let Ok(val) = val_str.parse::<i32>() {
if val > 0 {
return val;
}
}
debug::warning_print(format_args!(
"spglib: Could not parse SPGLIB_NUM_ATTEMPTS={}\n",
val_str
));
}
1000
}
pub fn del_delaunay_reduce(lattice: &Mat3, symprec: f64) -> Option<Mat3> {
debug::debug_print(format_args!(
"del_delaunay_reduce (tolerance = {}):\n",
symprec
));
delaunay_reduce(lattice, -1, symprec)
}
pub fn del_layer_delaunay_reduce(
lattice: &Mat3,
aperiodic_axis: Option<AperiodicAxis>,
symprec: f64,
) -> Option<Mat3> {
debug::debug_print(format_args!(
"del_layer_delaunay_reduce (tolerance = {}):\n",
symprec
));
let ap_i32 = aperiodic_axis.map_or(-1, |ap| ap.axis_index() as i32);
delaunay_reduce(lattice, ap_i32, symprec)
}
fn delaunay_reduce(
lattice: &Mat3,
aperiodic_axis: i32,
symprec: f64,
) -> Option<Mat3> {
let mut orig_lattice = [[0.0; 3]; 3];
orig_lattice = *lattice;
let mut basis: [[f64; 3]; 4] = [[0.0; 3]; 4];
let lattice_rank = get_extended_basis(&mut basis, lattice, aperiodic_axis);
let max_attempt = get_num_attempts();
let mut succeeded = false;
for attempt in 0..max_attempt {
debug::debug_print(format_args!(
"Trying delaunay_reduce_basis: attempt {}/{}\n",
attempt, max_attempt
));
if delaunay_reduce_basis(&mut basis, lattice_rank, symprec) {
succeeded = true;
break;
}
}
if !succeeded {
return None;
}
get_delaunay_shortest_vectors(&mut basis, lattice_rank, symprec);
let mut red_lattice = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
red_lattice[i][j] = basis[j][i];
}
}
if lattice_rank == 2 && aperiodic_axis != 2 {
let axis = aperiodic_axis as usize;
for i in 0..3 {
let temp = red_lattice[i][axis];
red_lattice[i][axis] = red_lattice[i][2]; red_lattice[i][2] = temp;
}
}
let volume = mat_get_determinant_d3(&red_lattice);
if volume.abs() < symprec {
debug::info_print(format_args!("spglib: Minimum lattice has no volume.\n"));
return None;
}
if volume < 0.0 {
for i in 0..3 {
for j in 0..3 {
red_lattice[i][j] = -red_lattice[i][j];
}
}
}
if let Ok(inv_red) = mat_inverse_matrix_d3(&red_lattice, symprec) {
let tmp_mat = mat_multiply_matrix_d3(&inv_red, &orig_lattice);
let tmp_mat_int = mat_cast_matrix_3d_to_3i(&tmp_mat);
if mat_get_determinant_i3(&tmp_mat_int).abs() != 1 {
debug::info_print(format_args!(
"spglib: Determinant of Delaunay change of basis matrix has to be 1 or -1.\n"
));
return None;
}
} else {
return None;
}
Some(red_lattice)
}
fn get_delaunay_shortest_vectors(basis: &mut [[f64; 3]; 4], lattice_rank: usize, symprec: f64) {
let mut b: [[f64; 3]; 7] = [[0.0; 3]; 7];
b[0] = basis[0];
b[1] = basis[1];
for i in 0..3 {
b[2][i] = basis[0][i] + basis[1][i];
}
b[3] = basis[2];
b[4] = basis[3];
for i in 0..3 {
b[5][i] = basis[1][i] + basis[2][i];
}
for i in 0..3 {
b[6][i] = basis[2][i] + basis[0][i];
}
if lattice_rank == 3 {
for _ in 0..6 {
for j in 0..6 {
if mat_norm_squared_d3(&b[j]) > mat_norm_squared_d3(&b[j + 1]) + ZERO_PREC {
let tmp = b[j];
b[j] = b[j + 1];
b[j + 1] = tmp;
}
}
}
} else {
for _ in 0..2 {
for j in 0..2 {
if mat_norm_squared_d3(&b[j]) > mat_norm_squared_d3(&b[j + 1]) + ZERO_PREC {
let tmp = b[j];
b[j] = b[j + 1];
b[j + 1] = tmp;
}
}
}
for _ in 3..6 { for j in 3..6 {
if mat_norm_squared_d3(&b[j]) > mat_norm_squared_d3(&b[j + 1]) + ZERO_PREC {
let tmp = b[j];
b[j] = b[j + 1];
b[j + 1] = tmp;
}
}
}
}
let mut tmpmat = [[0.0; 3]; 3];
for i in 2..7 {
for j in 0..3 {
tmpmat[j][0] = b[0][j];
tmpmat[j][1] = b[1][j];
tmpmat[j][2] = b[i][j];
}
if mat_get_determinant_d3(&tmpmat).abs() > symprec {
basis[0] = b[0];
basis[1] = b[1];
basis[2] = b[i];
return;
}
}
}
fn delaunay_reduce_basis(basis: &mut [[f64; 3]; 4], lattice_rank: usize, symprec: f64) -> bool {
for i in 0..3 {
for j in (i + 1)..4 {
let mut dot_product = 0.0;
for k in 0..3 {
dot_product += basis[i][k] * basis[j][k];
}
if dot_product > symprec {
if i < lattice_rank {
for k in 0..4 {
if k != i && k != j {
for l in 0..3 {
basis[k][l] += basis[i][l];
}
}
}
for k in 0..3 {
basis[i][k] = -basis[i][k];
}
return false; } else {
debug::info_print(format_args!(
"spglib: Dot product between basis {}, {} larger than 0.\n",
i + 1,
j + 1
));
debug::debug_print_vectors_d3(basis);
}
}
}
}
true }
fn get_extended_basis(basis: &mut [[f64; 3]; 4], lattice: &Mat3, aperiodic_axis: i32) -> usize {
let lattice_rank;
if aperiodic_axis == -1 {
lattice_rank = 3;
for i in 0..3 {
for j in 0..3 {
basis[i][j] = lattice[j][i];
}
}
} else {
lattice_rank = 2; let mut current_rank = 0;
for i in 0..3 {
if i != aperiodic_axis {
for j in 0..3 {
basis[current_rank][j] = lattice[j][i as usize];
}
current_rank += 1;
}
}
for j in 0..3 {
basis[lattice_rank][j] = lattice[j][aperiodic_axis as usize];
}
}
for i in 0..3 {
basis[3][i] = -lattice[i][0] - lattice[i][1] - lattice[i][2];
}
lattice_rank
}
pub fn del_layer_delaunay_reduce_2D(
red_lattice: &mut Mat3,
lattice: &Mat3,
unique_axis: i32,
aperiodic_axis: i32,
symprec: f64,
) -> bool {
debug::debug_print(format_args!("del_layer_delaunay_reduce_2D:\n"));
let mut j = -1;
let mut k = -1;
let lattice_rank;
if aperiodic_axis == -1 || unique_axis == aperiodic_axis {
for i in 0..3 {
if i != unique_axis {
if j == -1 {
j = i;
} else {
k = i;
}
}
}
lattice_rank = 2;
} else {
for i in 0..3 {
if i != unique_axis && i != aperiodic_axis {
j = i;
}
}
k = aperiodic_axis;
lattice_rank = 1;
}
if j < 0 || k < 0 {
return false;
}
let j = j as usize;
let k = k as usize;
let unique_axis = unique_axis as usize;
let mut unique_vec = [0.0; 3];
let mut lattice_2d = [[0.0; 2]; 3];
for i in 0..3 {
unique_vec[i] = lattice[i][unique_axis];
lattice_2d[i][0] = lattice[i][j];
lattice_2d[i][1] = lattice[i][k];
}
let mut basis: [[f64; 3]; 3] = [[0.0; 3]; 3];
get_extended_basis_2d(&mut basis, &lattice_2d);
let max_attempt = get_num_attempts();
let mut succeeded = false;
for attempt in 0..max_attempt {
debug::debug_print(format_args!(
"Trying delaunay_reduce_basis_2D: attempt {}/{}\n",
attempt, max_attempt
));
if delaunay_reduce_basis_2d(&mut basis, lattice_rank, symprec) {
succeeded = true;
break;
}
}
if !succeeded {
return false;
}
get_delaunay_shortest_vectors_2d(&mut basis, &unique_vec, lattice_rank, symprec);
for i in 0..3 {
red_lattice[i][unique_axis] = lattice[i][unique_axis];
red_lattice[i][j] = basis[0][i];
red_lattice[i][k] = basis[1][i];
}
let volume = mat_get_determinant_d3(red_lattice);
if volume.abs() < symprec {
debug::info_print(format_args!("spglib: Minimum lattice has no volume.\n"));
return false;
}
if volume < 0.0 {
for i in 0..3 {
red_lattice[i][unique_axis] = -red_lattice[i][unique_axis];
}
}
true
}
fn delaunay_reduce_basis_2d(basis: &mut [[f64; 3]; 3], lattice_rank: usize, symprec: f64) -> bool {
for i in 0..2 {
for j in (i + 1)..3 {
let mut dot_product = 0.0;
for k in 0..3 {
dot_product += basis[i][k] * basis[j][k];
}
if dot_product > symprec {
if i < lattice_rank {
for k in 0..3 {
if k != i && k != j {
for l in 0..3 {
basis[k][l] += 2.0 * basis[i][l];
}
break; }
}
for k in 0..3 {
basis[i][k] = -basis[i][k];
}
return false;
} else {
debug::info_print(format_args!(
"spglib: Dot product between basis {}, {} larger than 0.\n",
i + 1,
j + 1
));
debug::debug_print_vectors_d3(basis);
}
}
}
}
true
}
fn get_delaunay_shortest_vectors_2d(
basis: &mut [[f64; 3]; 3],
unique_vec: &[f64; 3],
lattice_rank: usize,
symprec: f64,
) {
let mut b: [[f64; 3]; 4] = [[0.0; 3]; 4];
for i in 0..3 {
b[i] = basis[i];
}
for i in 0..3 {
b[3][i] = basis[0][i] + basis[1][i];
}
let start_idx = lattice_rank % 2;
for _ in start_idx..3 {
for j in start_idx..3 {
if mat_norm_squared_d3(&b[j]) > mat_norm_squared_d3(&b[j + 1]) + ZERO_PREC {
let tmp = b[j];
b[j] = b[j + 1];
b[j + 1] = tmp;
}
}
}
let mut tmpmat = [[0.0; 3]; 3];
for i in 0..3 {
tmpmat[i][0] = b[0][i];
tmpmat[i][1] = unique_vec[i];
}
for i in 1..4 {
for j in 0..3 {
tmpmat[j][2] = b[i][j];
}
if mat_get_determinant_d3(&tmpmat).abs() > symprec {
basis[0] = b[0];
basis[1] = b[i];
break;
}
}
}
fn get_extended_basis_2d(basis: &mut [[f64; 3]; 3], lattice_2d: &[[f64; 2]; 3]) {
for i in 0..2 {
for j in 0..3 {
basis[i][j] = lattice_2d[j][i];
}
}
for i in 0..3 {
basis[2][i] = -lattice_2d[i][0] - lattice_2d[i][1];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delaunay_reduce_simple() {
let lattice: Mat3 = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let min_lattice = del_delaunay_reduce(&lattice, 1e-5);
assert!(min_lattice.is_some());
let min_lattice = min_lattice.unwrap();
assert!((min_lattice[0][0] - 1.0).abs() < 1e-5);
}
#[test]
fn test_delaunay_reduce_skewed() {
let lattice: Mat3 = [[1.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let min_lattice = del_delaunay_reduce(&lattice, 1e-5);
assert!(min_lattice.is_some());
let min_lattice = min_lattice.unwrap();
let dot_prod = min_lattice[0][0] * min_lattice[0][1]
+ min_lattice[1][0] * min_lattice[1][1]
+ min_lattice[2][0] * min_lattice[2][1];
assert!(dot_prod.abs() < 1e-5);
}
#[test]
fn test_get_extended_basis() {
let lattice: Mat3 = [[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 3.0]];
let mut basis: [[f64; 3]; 4] = [[0.0; 3]; 4];
get_extended_basis(&mut basis, &lattice, -1);
assert_eq!(basis[0], [1.0, 0.0, 0.0]);
assert_eq!(basis[3], [-1.0, -2.0, -3.0]);
}
}