use crate::cell::AperiodicAxis;
use crate::debug;
use crate::mathfunc::{Mat3, mat_get_metric, mat_multiply_matrix_d3};
use std::env;
pub const NIGGLI_MAJOR_VERSION: i32 = 2;
pub const NIGGLI_MINOR_VERSION: i32 = 0;
pub const NIGGLI_MICRO_VERSION: i32 = 0;
struct NiggliParams {
a: f64,
b: f64,
c: f64,
eta: f64,
xi: f64,
zeta: f64,
eps: f64,
l: i32,
m: i32,
n: i32,
tmat: Mat3, lattice: Mat3, }
impl NiggliParams {
fn new(lattice: &Mat3, eps: f64) -> Self {
NiggliParams {
a: 0.0,
b: 0.0,
c: 0.0,
eta: 0.0,
xi: 0.0,
zeta: 0.0,
eps,
l: 0,
m: 0,
n: 0,
tmat: [[0.0; 3]; 3],
lattice: *lattice,
}
}
}
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 niggli_get_major_version() -> i32 {
NIGGLI_MAJOR_VERSION
}
pub fn niggli_get_minor_version() -> i32 {
NIGGLI_MINOR_VERSION
}
pub fn niggli_get_micro_version() -> i32 {
NIGGLI_MICRO_VERSION
}
pub fn niggli_reduce(lattice: &mut Mat3, eps: f64, aperiodic_axis: Option<AperiodicAxis>) -> bool {
let mut p = NiggliParams::new(lattice, eps);
let mut succeeded = false;
if !((matches!(aperiodic_axis, Some(AperiodicAxis::X | AperiodicAxis::Y)) && layer_swap_axis(&mut p, aperiodic_axis.unwrap()))
|| (matches!(aperiodic_axis, None | Some(AperiodicAxis::Z)) && set_parameters(&mut p)))
{
return false;
}
let max_attempts = get_num_attempts();
for _ in 0..max_attempts {
if step1(&mut p) {
debug_show(1, &p);
if !reset(&mut p) {
return false;
}
}
let do_step2 = if aperiodic_axis.is_some() {
step2_for_layer(&mut p)
} else {
step2(&mut p)
};
if do_step2 {
debug_show(2, &p);
if !reset(&mut p) {
return false;
}
continue; }
if step3(&mut p) {
debug_show(3, &p);
if !reset(&mut p) {
return false;
}
}
if step4(&mut p) {
debug_show(4, &p);
if !reset(&mut p) {
return false;
}
}
if step5(&mut p) {
debug_show(5, &p);
if !reset(&mut p) {
return false;
}
continue; }
if step6(&mut p) {
debug_show(6, &p);
if !reset(&mut p) {
return false;
}
continue; }
if step7(&mut p) {
debug_show(7, &p);
if !reset(&mut p) {
return false;
}
continue; }
if step8(&mut p) {
debug_show(8, &p);
if !reset(&mut p) {
return false;
}
continue; }
succeeded = true;
break;
}
debug_show(-1, &p);
*lattice = p.lattice;
succeeded
}
fn layer_swap_axis(p: &mut NiggliParams, aperiodic_axis: AperiodicAxis) -> bool {
if aperiodic_axis == AperiodicAxis::X {
p.tmat = [[0.0, 0.0, -1.0], [0.0, -1.0, 0.0], [-1.0, 0.0, 0.0]];
} else { p.tmat = [[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, -1.0, 0.0]];
}
reset(p)
}
fn reset(p: &mut NiggliParams) -> bool {
p.lattice = mat_multiply_matrix_d3(&p.lattice, &p.tmat);
set_parameters(p)
}
fn set_parameters(p: &mut NiggliParams) -> bool {
let g = mat_get_metric(&p.lattice);
p.a = g[0][0];
p.b = g[1][1];
p.c = g[2][2];
p.xi = g[1][2] * 2.0;
p.eta = g[0][2] * 2.0;
p.zeta = g[0][1] * 2.0;
set_angle_types(p);
true
}
fn set_angle_types(p: &mut NiggliParams) {
p.l = 0;
p.m = 0;
p.n = 0;
if p.xi < -p.eps {
p.l = -1;
}
if p.xi > p.eps {
p.l = 1;
}
if p.eta < -p.eps {
p.m = -1;
}
if p.eta > p.eps {
p.m = 1;
}
if p.zeta < -p.eps {
p.n = -1;
}
if p.zeta > p.eps {
p.n = 1;
}
}
fn debug_show(j: i32, p: &NiggliParams) {
if j < 0 {
debug::debug_print(format_args!("Finish: "));
} else {
debug::debug_print(format_args!("Step {}: ", j));
}
debug::debug_print(format_args!(
"{:.6} {:.6} {:.6} {:.6} {:.6} {:.6}\n",
p.a, p.b, p.c, p.xi, p.eta, p.zeta
));
}
fn step1(p: &mut NiggliParams) -> bool {
if p.a > p.b + p.eps || ((p.a - p.b).abs() <= p.eps && p.xi.abs() > p.eta.abs() + p.eps) {
p.tmat = [[0.0, -1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, -1.0]];
true
} else {
false
}
}
fn step2(p: &mut NiggliParams) -> bool {
if p.b > p.c + p.eps || ((p.b - p.c).abs() <= p.eps && p.eta.abs() > p.zeta.abs() + p.eps) {
p.tmat = [[-1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, -1.0, 0.0]];
true
} else {
false
}
}
fn step2_for_layer(p: &mut NiggliParams) -> bool {
if p.b > p.c + p.eps || ((p.b - p.c).abs() <= p.eps && p.eta.abs() > p.zeta.abs() + p.eps) {
debug::debug_print(format_args!(
"niggli: B > C or B = C and |eta| > |zeta|. Please elongate the aperiodic axis.\n"
));
}
false
}
fn step3(p: &mut NiggliParams) -> bool {
if p.l * p.m * p.n == 1 {
let i = if p.l == -1 { -1.0 } else { 1.0 };
let j = if p.m == -1 { -1.0 } else { 1.0 };
let k = if p.n == -1 { -1.0 } else { 1.0 };
p.tmat = [[i, 0.0, 0.0], [0.0, j, 0.0], [0.0, 0.0, k]];
true
} else {
false
}
}
fn step4(p: &mut NiggliParams) -> bool {
if p.l == -1 && p.m == -1 && p.n == -1 {
return false;
}
if p.l * p.m * p.n == 0 || p.l * p.m * p.n == -1 {
let mut i = 1.0;
let mut j = 1.0;
let mut k = 1.0;
let mut r = -1;
if p.l == 1 {
i = -1.0;
}
if p.l == 0 {
r = 0;
}
if p.m == 1 {
j = -1.0;
}
if p.m == 0 {
r = 1;
}
if p.n == 1 {
k = -1.0;
}
if p.n == 0 {
r = 2;
}
if i * j * k == -1.0 {
if r == 0 {
i = -1.0;
}
if r == 1 {
j = -1.0;
}
if r == 2 {
k = -1.0;
}
}
p.tmat = [[i, 0.0, 0.0], [0.0, j, 0.0], [0.0, 0.0, k]];
true
} else {
false
}
}
fn step5(p: &mut NiggliParams) -> bool {
if p.xi.abs() > p.b + p.eps
|| ((p.b - p.xi).abs() <= p.eps && 2.0 * p.eta < p.zeta - p.eps)
|| ((p.b + p.xi).abs() <= p.eps && p.zeta < -p.eps)
{
p.tmat = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
if p.xi > 0.0 {
p.tmat[1][2] = -1.0; }
if p.xi < 0.0 {
p.tmat[1][2] = 1.0;
}
true
} else {
false
}
}
fn step6(p: &mut NiggliParams) -> bool {
if p.eta.abs() > p.a + p.eps
|| ((p.a - p.eta).abs() <= p.eps && 2.0 * p.xi < p.zeta - p.eps)
|| ((p.a + p.eta).abs() <= p.eps && p.zeta < -p.eps)
{
p.tmat = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
if p.eta > 0.0 {
p.tmat[0][2] = -1.0; }
if p.eta < 0.0 {
p.tmat[0][2] = 1.0;
}
true
} else {
false
}
}
fn step7(p: &mut NiggliParams) -> bool {
if p.zeta.abs() > p.a + p.eps
|| ((p.a - p.zeta).abs() <= p.eps && 2.0 * p.xi < p.eta - p.eps)
|| ((p.a + p.zeta).abs() <= p.eps && p.eta < -p.eps)
{
p.tmat = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
if p.zeta > 0.0 {
p.tmat[0][1] = -1.0; }
if p.zeta < 0.0 {
p.tmat[0][1] = 1.0;
}
true
} else {
false
}
}
fn step8(p: &mut NiggliParams) -> bool {
if p.xi + p.eta + p.zeta + p.a + p.b < -p.eps
|| ((p.xi + p.eta + p.zeta + p.a + p.b).abs() <= p.eps
&& 2.0 * (p.a + p.eta) + p.zeta > p.eps)
{
p.tmat = [[1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0]];
true
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_niggli_reduce_identity() {
let mut lattice = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let res = niggli_reduce(&mut lattice, 1e-5, None);
assert!(res);
assert!((lattice[0][0] - 1.0).abs() < 1e-5);
}
#[test]
fn test_niggli_reduce_swap() {
let mut lattice = [[2.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let res = niggli_reduce(&mut lattice, 1e-5, None);
assert!(res);
let g = mat_get_metric(&lattice);
assert!((g[0][0] - 1.0).abs() < 1e-5); assert!((g[1][1] - 1.0).abs() < 1e-5); assert!((g[2][2] - 4.0).abs() < 1e-5); }
}