use std::collections::HashSet;
use nalgebra::{Dyn, Matrix3, OMatrix, U3, Vector3};
use serde::Serialize;
use super::point_group::{iter_trans_mat_basis, iter_unimodular_trans_mat};
use super::rotation_type::identify_rotation_type;
use super::space_group::match_origin_shift;
use crate::base::{
AngleTolerance, Lattice, MoyoError, Operation, Operations, Rotation, Translation,
UnimodularTransformation, project_rotations,
};
use crate::math::SNF;
use crate::search::search_bravais_group;
pub fn integral_normalizer(
prim_operations: &Operations,
prim_generators: &Operations,
epsilon: f64,
) -> Vec<UnimodularTransformation> {
let prim_rotations = project_rotations(prim_operations);
let prim_rotation_generators = project_rotations(prim_generators);
let rotation_types = prim_rotations
.iter()
.map(identify_rotation_type)
.collect::<Vec<_>>();
let mut conjugators = vec![];
for trans_mat_basis in
iter_trans_mat_basis(prim_rotations, rotation_types, prim_rotation_generators)
{
for prim_trans_mat in iter_unimodular_trans_mat(trans_mat_basis) {
if let Some(origin_shift) =
match_origin_shift(prim_operations, &prim_trans_mat, prim_generators, epsilon)
{
conjugators.push(UnimodularTransformation::new(prim_trans_mat, origin_shift));
break;
}
}
}
conjugators
}
#[derive(Debug, Clone, Serialize)]
pub struct Normalizer {
pub translations: Vec<Translation>,
pub coset_representatives: Vec<UnimodularTransformation>,
pub continuous_translation_directions: Vec<Vector3<f64>>,
}
impl Normalizer {
pub fn from_lattice(
prim_lattice: &Lattice,
prim_operations: &Operations,
prim_generators: &Operations,
symprec: f64,
angle_tolerance: AngleTolerance,
preserve_chirality: bool,
) -> Result<Self, MoyoError> {
let (reduced_lattice, reduced_trans_mat) = prim_lattice.minkowski_reduce()?;
let to_reduced = UnimodularTransformation::from_linear(reduced_trans_mat);
let reduced_operations = to_reduced.transform_operations(prim_operations);
let reduced_generators = to_reduced.transform_operations(prim_generators);
let reduced_rotations = project_rotations(&reduced_operations);
let reduced_rotation_set: HashSet<Rotation> = reduced_rotations.iter().copied().collect();
let bravais = search_bravais_group(&reduced_lattice, symprec, angle_tolerance)?;
let epsilon = symprec / reduced_lattice.volume().powf(1.0 / 3.0);
let mut coset_representatives_reduced = Vec::new();
for p in &bravais {
let p_f = p.map(|e| e as f64);
if preserve_chirality && p_f.determinant().round() as i32 != 1 {
continue;
}
let p_inv = p_f.try_inverse().unwrap().map(|e| e.round() as i32);
let preserves = reduced_rotations.iter().all(|w| {
let conjugated = p_inv * w * p;
reduced_rotation_set.contains(&conjugated)
});
if !preserves {
continue;
}
if let Some(origin_shift) =
match_origin_shift(&reduced_operations, p, &reduced_generators, epsilon)
{
coset_representatives_reduced.push(UnimodularTransformation::new(*p, origin_shift));
}
}
let (translations_reduced, continuous_reduced) =
normalizer_translations(&reduced_rotations);
let to_input = to_reduced.inverse();
let coset_representatives = coset_representatives_reduced
.iter()
.map(|cr| {
let op = Operation::new(cr.linear, cr.origin_shift);
let mapped = to_input.transform_operation(&op);
UnimodularTransformation::new(
mapped.rotation,
mapped.translation.map(|e| e.rem_euclid(1.0)),
)
})
.collect();
let trans_mat_f = to_reduced.linear_as_f64();
let translations = translations_reduced
.iter()
.map(|t| (trans_mat_f * t).map(|e| e.rem_euclid(1.0)))
.collect();
let continuous_translation_directions =
continuous_reduced.iter().map(|d| trans_mat_f * d).collect();
Ok(Self {
translations,
coset_representatives,
continuous_translation_directions,
})
}
}
fn normalizer_translations(prim_rotations: &[Rotation]) -> (Vec<Translation>, Vec<Vector3<f64>>) {
if prim_rotations.is_empty() {
return (Vec::new(), Vec::new());
}
let n = prim_rotations.len();
let mut a = OMatrix::<i32, Dyn, U3>::zeros(3 * n);
for (k, w) in prim_rotations.iter().enumerate() {
let wmi = w - Matrix3::<i32>::identity();
for i in 0..3 {
for j in 0..3 {
a[(3 * k + i, j)] = wmi[(i, j)];
}
}
}
let snf = SNF::new(&a);
let mut discrete = Vec::new();
let mut continuous = Vec::new();
for i in 0..3 {
let column = Vector3::new(snf.r[(0, i)], snf.r[(1, i)], snf.r[(2, i)]);
match snf.d[(i, i)] {
0 => continuous.push(column.map(|e| e as f64)),
d if d > 1 => {
let t = column.map(|e| (e as f64) / (d as f64));
discrete.push(t.map(|e| e.rem_euclid(1.0)));
}
_ => {}
}
}
(discrete, continuous)
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use nalgebra::matrix;
use super::*;
use crate::base::{AngleTolerance, Lattice, Operation, UnimodularLinear, traverse};
use crate::data::HallSymbol;
const TEST_SYMPREC: f64 = 1e-4;
const TEST_ANGLE: AngleTolerance = AngleTolerance::Default;
fn cubic_lattice(a: f64) -> Lattice {
Lattice::new(matrix![
a, 0.0, 0.0;
0.0, a, 0.0;
0.0, 0.0, a;
])
}
fn hexagonal_lattice(a: f64, c: f64) -> Lattice {
let s = (3.0_f64).sqrt() / 2.0;
Lattice::new(matrix![
a, 0.0, 0.0;
-0.5 * a, s * a, 0.0;
0.0, 0.0, c;
])
}
fn orthorhombic_lattice(a: f64, b: f64, c: f64) -> Lattice {
Lattice::new(matrix![
a, 0.0, 0.0;
0.0, b, 0.0;
0.0, 0.0, c;
])
}
fn assert_normalizer_conjugates_g(
prim_lattice: &Lattice,
prim_operations: &Operations,
prim_generators: &Operations,
preserve_chirality: bool,
) -> Normalizer {
let normalizer = Normalizer::from_lattice(
prim_lattice,
prim_operations,
prim_generators,
TEST_SYMPREC,
TEST_ANGLE,
preserve_chirality,
)
.unwrap();
let rotations: HashSet<Rotation> = prim_operations.iter().map(|op| op.rotation).collect();
for cr in &normalizer.coset_representatives {
let p = cr.linear;
let p_f = p.map(|e| e as f64);
let p_inv = p_f.try_inverse().unwrap().map(|e| e.round() as i32);
let conjugated: HashSet<Rotation> = rotations.iter().map(|w| p_inv * w * p).collect();
assert_eq!(
rotations, conjugated,
"coset rep {:?} did not conjugate G to itself",
cr
);
}
assert!(
normalizer.coset_representatives.iter().any(|cr| {
cr.linear == UnimodularLinear::identity()
&& cr
.origin_shift
.iter()
.all(|&v| (v - v.round()).abs() < 1e-6)
}),
"identity not present in coset representatives"
);
normalizer
}
#[test]
fn test_normalizer_p1() {
let hall_symbol = HallSymbol::from_hall_number(1).unwrap();
let prim_operations = hall_symbol.primitive_traverse();
let prim_generators = hall_symbol.primitive_generators();
let normalizer = Normalizer::from_lattice(
&cubic_lattice(1.0),
&prim_operations,
&prim_generators,
TEST_SYMPREC,
TEST_ANGLE,
false,
)
.unwrap();
assert_eq!(normalizer.coset_representatives.len(), 48);
assert_eq!(normalizer.continuous_translation_directions.len(), 3);
}
#[test]
fn test_normalizer_p_minus_1() {
let hall_symbol = HallSymbol::from_hall_number(2).unwrap();
let prim_operations = hall_symbol.primitive_traverse();
let prim_generators = hall_symbol.primitive_generators();
let normalizer = assert_normalizer_conjugates_g(
&cubic_lattice(1.0),
&prim_operations,
&prim_generators,
false,
);
assert_eq!(normalizer.continuous_translation_directions.len(), 0);
assert_eq!(normalizer.coset_representatives.len(), 48);
}
#[test]
fn test_normalizer_pmm2() {
use nalgebra::matrix;
let identity = Operation::new(Rotation::identity(), Translation::zeros());
let two_z = Operation::new(matrix![-1, 0, 0; 0, -1, 0; 0, 0, 1], Translation::zeros());
let m_x = Operation::new(matrix![-1, 0, 0; 0, 1, 0; 0, 0, 1], Translation::zeros());
let m_y = Operation::new(matrix![1, 0, 0; 0, -1, 0; 0, 0, 1], Translation::zeros());
let prim_operations = vec![identity, two_z, m_x.clone(), m_y];
let prim_generators = vec![m_x];
let normalizer = assert_normalizer_conjugates_g(
&orthorhombic_lattice(1.0, 1.3, 1.7),
&prim_operations,
&prim_generators,
false,
);
assert_eq!(normalizer.continuous_translation_directions.len(), 1);
}
#[test]
fn test_normalizer_pmmm() {
use nalgebra::matrix;
let identity = Rotation::identity();
let two_z = matrix![-1, 0, 0; 0, -1, 0; 0, 0, 1];
let two_x = matrix![1, 0, 0; 0, -1, 0; 0, 0, -1];
let two_y = matrix![-1, 0, 0; 0, 1, 0; 0, 0, -1];
let inv = -identity;
let m_x = -two_x;
let m_y = -two_y;
let m_z = -two_z;
let zero = Translation::zeros();
let prim_operations: Operations = [identity, two_x, two_y, two_z, inv, m_x, m_y, m_z]
.iter()
.map(|r| Operation::new(*r, zero))
.collect();
let prim_generators = vec![
Operation::new(two_x, zero),
Operation::new(two_y, zero),
Operation::new(inv, zero),
];
let normalizer = assert_normalizer_conjugates_g(
&orthorhombic_lattice(1.0, 1.3, 1.7),
&prim_operations,
&prim_generators,
false,
);
assert_eq!(normalizer.continuous_translation_directions.len(), 0);
}
#[test]
fn test_chirality_preserving_for_sohncke() {
let hall_symbol = HallSymbol::from_hall_number(517).unwrap();
let prim_operations = hall_symbol.primitive_traverse();
let prim_generators = hall_symbol.primitive_generators();
let full = Normalizer::from_lattice(
&cubic_lattice(1.0),
&prim_operations,
&prim_generators,
TEST_SYMPREC,
TEST_ANGLE,
false,
)
.unwrap();
let preserving = Normalizer::from_lattice(
&cubic_lattice(1.0),
&prim_operations,
&prim_generators,
TEST_SYMPREC,
TEST_ANGLE,
true,
)
.unwrap();
assert!(preserving.coset_representatives.len() < full.coset_representatives.len());
for cr in &preserving.coset_representatives {
let det = cr.linear.map(|e| e as f64).determinant().round() as i32;
assert_eq!(det, 1);
}
}
#[test]
fn test_normalizer_p43n() {
let hall_symbol = HallSymbol::from_hall_number(515).unwrap();
let prim_operations = hall_symbol.primitive_traverse();
let prim_generators = hall_symbol.primitive_generators();
let normalizer = assert_normalizer_conjugates_g(
&cubic_lattice(1.0),
&prim_operations,
&prim_generators,
false,
);
assert!(48 % normalizer.coset_representatives.len() == 0);
}
#[test]
fn test_normalizer_translation_subgroup_contains_z3() {
let hall_symbol = HallSymbol::from_hall_number(515).unwrap();
let prim_operations = hall_symbol.primitive_traverse();
let prim_generators = hall_symbol.primitive_generators();
let normalizer = Normalizer::from_lattice(
&cubic_lattice(1.0),
&prim_operations,
&prim_generators,
TEST_SYMPREC,
TEST_ANGLE,
false,
)
.unwrap();
for t in &normalizer.translations {
let shifted: Operations = prim_operations
.iter()
.map(|op| {
let new_t = (op.translation
+ (op.rotation - Matrix3::<i32>::identity()).map(|e| e as f64) * t)
.map(|e| e.rem_euclid(1.0));
Operation::new(op.rotation, new_t)
})
.collect();
let original: HashSet<(Rotation, [i64; 3])> = prim_operations
.iter()
.map(|op| (op.rotation, quantize_translation(&op.translation)))
.collect();
let new_set: HashSet<(Rotation, [i64; 3])> = shifted
.iter()
.map(|op| (op.rotation, quantize_translation(&op.translation)))
.collect();
assert_eq!(original, new_set);
}
}
fn quantize_translation(t: &Vector3<f64>) -> [i64; 3] {
let denom: i64 = 24;
let denom_f = denom as f64;
[
((t[0] * denom_f).round() as i64).rem_euclid(denom),
((t[1] * denom_f).round() as i64).rem_euclid(denom),
((t[2] * denom_f).round() as i64).rem_euclid(denom),
]
}
#[test]
fn test_normalizer_hexagonal_p6mm() {
use nalgebra::matrix;
let six_z = matrix![1, -1, 0; 1, 0, 0; 0, 0, 1];
let m_x = matrix![1, -1, 0; 0, -1, 0; 0, 0, 1];
let prim_operations: Operations = traverse(&vec![six_z, m_x])
.iter()
.map(|r| Operation::new(*r, Translation::zeros()))
.collect();
assert_eq!(prim_operations.len(), 12);
let prim_generators = vec![
Operation::new(six_z, Translation::zeros()),
Operation::new(m_x, Translation::zeros()),
];
let normalizer = assert_normalizer_conjugates_g(
&hexagonal_lattice(1.0, 1.5),
&prim_operations,
&prim_generators,
false,
);
assert_eq!(normalizer.continuous_translation_directions.len(), 1);
}
}