use itertools::iproduct;
use log::debug;
use nalgebra::Vector3;
use std::collections::HashMap;
use crate::base::{
Lattice, MoyoError, Permutation, Position, UnimodularTransformation, orbits_from_permutations,
};
use crate::data::{HallNumber, WyckoffPosition, WyckoffPositionSpace, iter_wyckoff_positions};
use crate::math::SNF;
pub fn orbits_in_cell(
prim_num_atoms: usize,
prim_permutations: &[Permutation],
site_mapping: &[usize],
) -> Vec<usize> {
let prim_orbits = orbits_from_permutations(prim_num_atoms, prim_permutations);
let num_atoms = site_mapping.len();
let mut map = HashMap::new();
let mut orbits = vec![]; for i in 0..num_atoms {
let key = prim_orbits[site_mapping[i]]; map.entry(key).or_insert(i);
orbits.push(*map.get(&key).unwrap());
}
orbits
}
pub(super) struct OrbitGrouping {
pub mapping: Vec<usize>,
pub remapping: Vec<usize>,
pub multiplicities: Vec<usize>,
}
pub(super) fn group_sites_by_orbit(
prim_num_atoms: usize,
prim_permutations: &[Permutation],
site_mapping: &[usize],
std_num_atoms: usize,
) -> OrbitGrouping {
let orbits = orbits_in_cell(prim_num_atoms, prim_permutations, site_mapping);
let mut num_orbits = 0;
let mut mapping = vec![0; std_num_atoms];
let mut remapping = vec![];
for i in 0..std_num_atoms {
if orbits[i] == i {
mapping[i] = num_orbits;
remapping.push(i);
num_orbits += 1;
} else {
mapping[i] = mapping[orbits[i]];
}
}
let mut multiplicities = vec![0; num_orbits];
for i in 0..std_num_atoms {
multiplicities[mapping[i]] += 1;
}
OrbitGrouping {
mapping,
remapping,
multiplicities,
}
}
pub(super) fn assign_wyckoffs_by_orbit<W, F>(
group: &OrbitGrouping,
positions: &[Position],
mut find_for_orbit: F,
) -> Result<Vec<W>, MoyoError>
where
W: Clone,
F: FnMut(&Position, usize) -> Option<W>,
{
let mut representative_wyckoffs: Vec<Option<W>> = vec![None; group.multiplicities.len()];
for (i, position) in positions.iter().enumerate() {
let orbit = group.mapping[i];
if representative_wyckoffs[orbit].is_some() {
continue;
}
if let Some(w) = find_for_orbit(position, group.multiplicities[orbit]) {
representative_wyckoffs[orbit] = Some(w);
}
}
for (orbit, wyckoff) in representative_wyckoffs.iter().enumerate() {
if wyckoff.is_none() {
debug!(
"Failed to assign Wyckoff position with multiplicity {} at representative site {}",
group.multiplicities[orbit], group.remapping[orbit]
);
}
}
let representative_wyckoffs = representative_wyckoffs
.into_iter()
.map(|w| w.ok_or(MoyoError::WyckoffPositionAssignmentError))
.collect::<Result<Vec<_>, _>>()?;
Ok(group
.mapping
.iter()
.map(|&orbit| representative_wyckoffs[orbit].clone())
.collect())
}
pub(crate) fn wyckoff_positions_under_normalizer(
conventional_ops: &[UnimodularTransformation],
lattice: &Lattice,
positions: &[Position],
hall_number: HallNumber,
site_orbits: &[usize],
orbit_multiplicities: &[usize],
symprec: f64,
) -> Result<Vec<Vec<WyckoffPosition>>, MoyoError> {
let num_orbits = orbit_multiplicities.len();
let mut result = Vec::with_capacity(conventional_ops.len());
for op in conventional_ops {
let linear = op.linear.map(|e| e as f64);
let mut representative_wyckoffs: Vec<Option<WyckoffPosition>> = vec![None; num_orbits];
for (i, &orbit) in site_orbits.iter().enumerate() {
if representative_wyckoffs[orbit].is_some() {
continue;
}
let transformed = (linear * positions[i] + op.origin_shift).map(|e| e.rem_euclid(1.0));
if let Some(wyckoff) = iter_wyckoff_positions(hall_number, orbit_multiplicities[orbit])
.find(|w| match_wyckoff_coordinates(&transformed, w.coordinates, lattice, symprec))
.cloned()
{
representative_wyckoffs[orbit] = Some(wyckoff);
}
}
let representative_wyckoffs = representative_wyckoffs
.into_iter()
.map(|w| w.ok_or(MoyoError::WyckoffPositionAssignmentError))
.collect::<Result<Vec<_>, _>>()?;
let assignment = site_orbits
.iter()
.map(|&orbit| representative_wyckoffs[orbit].clone())
.collect();
result.push(assignment);
}
Ok(result)
}
pub(super) fn match_wyckoff_coordinates(
position: &Position,
coordinates: &str,
lattice: &Lattice,
symprec: f64,
) -> bool {
let space = WyckoffPositionSpace::new(coordinates);
let snf = SNF::new(&space.linear);
let iter_multi_1 = iproduct!(-1..=1, -1..=1, -1..=1);
let iter_multi_2 = iproduct!(-2_i32..=2_i32, -2_i32..=2_i32, -2_i32..=2_i32)
.filter(|&(n1, n2, n3)| n1.abs() == 2 || n2.abs() == 2 || n3.abs() == 2);
for offset in iter_multi_1.chain(iter_multi_2) {
let offset = Vector3::new(offset.0 as f64, offset.1 as f64, offset.2 as f64);
let b = snf.l.map(|e| e as f64) * (offset + position - space.origin);
let mut rinvy = Vector3::zeros();
for i in 0..3 {
if snf.d[(i, i)] != 0 {
rinvy[i] = b[i] / snf.d[(i, i)] as f64;
}
}
let y = snf.r.map(|e| e as f64) * rinvy;
let diff = space.linear.map(|e| e as f64) * y + space.origin - position - offset;
if lattice.cartesian_coords(&diff).norm() < symprec {
return true;
}
}
false
}