use std::f64::consts::PI;
use log::debug;
use nalgebra::{Matrix3, Vector3};
use serde::{Deserialize, Serialize};
use super::super::error::MoyoError;
use super::super::lattice::Lattice;
use super::super::tolerance::{AngleTolerance, is_angle_within_tolerance};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayerLattice {
inner: Lattice,
}
impl LayerLattice {
pub fn new(
lattice: Lattice,
symprec: f64,
angle_tolerance: AngleTolerance,
) -> Result<Self, MoyoError> {
let a = lattice.basis.column(0);
let b = lattice.basis.column(1);
let c = lattice.basis.column(2);
let na = a.norm();
let nb = b.norm();
let nc = c.norm();
if na == 0.0 || nb == 0.0 || nc == 0.0 {
return Err(MoyoError::DegenerateLattice);
}
let dev_ca = c.angle(&a) - PI / 2.0;
let dev_cb = c.angle(&b) - PI / 2.0;
let ok_ca = is_angle_within_tolerance(dev_ca, nc, na, symprec, angle_tolerance);
let ok_cb = is_angle_within_tolerance(dev_cb, nc, nb, symprec, angle_tolerance);
if !ok_ca || !ok_cb {
debug!(
"Aperiodic axis is not orthogonal: dev(c,a)={:.6} rad, dev(c,b)={:.6} rad",
dev_ca, dev_cb
);
return Err(MoyoError::AperiodicAxisNotOrthogonal {
dev_ca: dev_ca.abs(),
dev_cb: dev_cb.abs(),
});
}
let z_axis = Vector3::new(0.0_f64, 0.0, 1.0);
let dev_az = a.angle(&z_axis) - PI / 2.0;
let dev_bz = b.angle(&z_axis) - PI / 2.0;
let ok_az = is_angle_within_tolerance(dev_az, na, 1.0, symprec, angle_tolerance);
let ok_bz = is_angle_within_tolerance(dev_bz, nb, 1.0, symprec, angle_tolerance);
if !ok_az || !ok_bz {
debug!(
"In-plane axes are not in the xy-plane: dev(a,xy)={:.6} rad, dev(b,xy)={:.6} rad",
dev_az, dev_bz
);
return Err(MoyoError::InPlaneAxesNotInXY {
dev_az: dev_az.abs(),
dev_bz: dev_bz.abs(),
});
}
Ok(Self { inner: lattice })
}
pub(crate) fn new_unchecked(lattice: Lattice) -> Self {
Self { inner: lattice }
}
pub fn basis(&self) -> &Matrix3<f64> {
&self.inner.basis
}
pub(crate) fn as_lattice(&self) -> Lattice {
Lattice {
basis: self.inner.basis,
}
}
}
#[cfg(test)]
mod tests {
use nalgebra::matrix;
use super::*;
const TEST_SYMPREC: f64 = 1e-4;
#[test]
fn test_layer_lattice_new_orthogonal() {
let lattice = Lattice::new(matrix![
1.0, 0.0, 0.0;
0.0, 1.0, 0.0;
0.0, 0.0, 5.0;
]);
let ll = LayerLattice::new(lattice, TEST_SYMPREC, AngleTolerance::Default).unwrap();
assert_eq!(ll.basis().column(2)[2], 5.0);
}
#[test]
fn test_layer_lattice_new_oblique_in_plane_ok() {
let lattice = Lattice::new(matrix![
1.0, 0.0, 0.0;
-0.5, (3.0_f64).sqrt() / 2.0, 0.0;
0.0, 0.0, 5.0;
]);
assert!(LayerLattice::new(lattice, TEST_SYMPREC, AngleTolerance::Default).is_ok());
}
#[test]
fn test_layer_lattice_new_rejects_tilted_c() {
let lattice = Lattice::new(matrix![
1.0, 0.0, 0.0;
0.0, 1.0, 0.0;
0.5, 0.0, 5.0;
]);
assert!(matches!(
LayerLattice::new(lattice, TEST_SYMPREC, AngleTolerance::Default),
Err(MoyoError::AperiodicAxisNotOrthogonal { .. })
));
}
#[test]
fn test_layer_lattice_new_explicit_radian() {
let lattice_factory = || {
Lattice::new(matrix![
1.0, 0.0, 0.0;
0.0, 1.0, 0.0;
0.15, 0.0, 5.0;
])
};
assert!(
LayerLattice::new(
lattice_factory(),
TEST_SYMPREC,
AngleTolerance::Radian(5.0_f64.to_radians()),
)
.is_ok()
);
assert!(matches!(
LayerLattice::new(
lattice_factory(),
TEST_SYMPREC,
AngleTolerance::Radian(1.0_f64.to_radians()),
),
Err(MoyoError::AperiodicAxisNotOrthogonal { .. })
));
}
#[test]
fn test_layer_lattice_new_rejects_globally_rotated() {
let s = (0.5_f64).sqrt();
let lattice = Lattice::new(matrix![
s, 0.0, -s;
0.0, 1.0, 0.0;
s, 0.0, s;
]);
assert!(matches!(
LayerLattice::new(lattice, TEST_SYMPREC, AngleTolerance::Default),
Err(MoyoError::InPlaneAxesNotInXY { .. })
));
}
#[test]
fn test_layer_lattice_new_rejects_zero_norm() {
let lattice = Lattice::new(matrix![
1.0, 0.0, 0.0;
0.0, 1.0, 0.0;
0.0, 0.0, 0.0;
]);
assert!(matches!(
LayerLattice::new(lattice, TEST_SYMPREC, AngleTolerance::Default),
Err(MoyoError::DegenerateLattice)
));
}
}