use std::collections::HashMap;
use nalgebra::Vector3;
use crate::base::{
Linear, MoyoError, Operation, Operations, Rotation, Transformation, lattice_points,
};
use crate::subgroup::finite_group::FiniteGroup;
use crate::subgroup::klassengleiche_subgroup::KlassengleicheSubgroup;
pub(super) struct AffineQuotient {
pub(super) group: FiniteGroup,
operations: Operations,
pub(super) parent_operations: Operations,
point_indices: Vec<usize>,
transformation: Linear,
sublattice_index: usize,
point_order: usize,
}
impl AffineQuotient {
pub(super) fn new(
prim_operations: &Operations,
transformation: &Linear,
epsilon: f64,
) -> Result<Self, MoyoError> {
let determinant = exact_determinant(transformation);
if determinant <= 0 || determinant > i32::MAX as i128 {
return Err(MoyoError::InvalidSublatticeTransformationError);
}
let sublattice_index = determinant as usize;
let lattice_points = lattice_points(transformation);
if lattice_points.len() != sublattice_index {
return Err(MoyoError::InvalidSublatticeTransformationError);
}
let to_sublattice = Transformation::from_linear(*transformation);
let quotient_order = prim_operations.len() * sublattice_index;
let mut operations = Vec::with_capacity(quotient_order);
let mut parent_operations = Vec::with_capacity(quotient_order);
let mut point_indices = Vec::with_capacity(quotient_order);
for (point_index, operation) in prim_operations.iter().enumerate() {
for lattice_point in &lattice_points {
let parent_operation = Operation::new(
operation.rotation,
operation.translation + lattice_point.map(|element| element as f64),
);
let mut transformed = to_sublattice
.transform_operation(&parent_operation)
.ok_or(MoyoError::InvalidSublatticeTransformationError)?;
transformed.translation = transformed
.translation
.map(|element| reduce_mod_one(element, epsilon));
operations.push(transformed);
parent_operations.push(parent_operation);
point_indices.push(point_index);
}
}
let table = affine_cayley_table(&operations, epsilon)
.ok_or(MoyoError::InvalidSublatticeTransformationError)?;
let group = FiniteGroup::from_table(table)
.ok_or(MoyoError::InvalidSublatticeTransformationError)?;
Ok(Self {
group,
operations,
parent_operations,
point_indices,
transformation: *transformation,
sublattice_index,
point_order: prim_operations.len(),
})
}
pub(super) fn is_complement(&self, subgroup: &[usize]) -> bool {
if subgroup.len() != self.point_order {
return false;
}
let mut covered = vec![false; self.point_order];
for &index in subgroup {
let point_index = self.point_indices[index];
if covered[point_index] {
return false;
}
covered[point_index] = true;
}
covered.into_iter().all(|value| value)
}
pub(super) fn make_subgroup(&self, indices: &[usize]) -> KlassengleicheSubgroup {
KlassengleicheSubgroup {
operations: indices
.iter()
.map(|&index| self.operations[index].clone())
.collect(),
parent_operations: indices
.iter()
.map(|&index| self.parent_operations[index].clone())
.collect(),
operation_indices: indices
.iter()
.map(|&index| self.point_indices[index])
.collect(),
transformation: self.transformation,
klassengleiche_index: self.sublattice_index,
}
}
}
fn exact_determinant(matrix: &Linear) -> i128 {
let value = |row, column| matrix[(row, column)] as i128;
value(0, 0) * (value(1, 1) * value(2, 2) - value(1, 2) * value(2, 1))
- value(0, 1) * (value(1, 0) * value(2, 2) - value(1, 2) * value(2, 0))
+ value(0, 2) * (value(1, 0) * value(2, 1) - value(1, 1) * value(2, 0))
}
fn affine_cayley_table(operations: &Operations, epsilon: f64) -> Option<Vec<Vec<usize>>> {
let mut rotation_indices = HashMap::<Rotation, Vec<usize>>::new();
for (index, operation) in operations.iter().enumerate() {
let candidates = rotation_indices.entry(operation.rotation).or_default();
if candidates.iter().any(|&candidate| {
translations_equivalent(
&operation.translation,
&operations[candidate].translation,
epsilon,
)
}) {
return None;
}
candidates.push(index);
}
let mut table = vec![vec![0; operations.len()]; operations.len()];
for (lhs_index, lhs) in operations.iter().enumerate() {
for (rhs_index, rhs) in operations.iter().enumerate() {
let product = lhs.clone() * rhs.clone();
let product_index =
rotation_indices
.get(&product.rotation)?
.iter()
.find(|&&candidate| {
translations_equivalent(
&product.translation,
&operations[candidate].translation,
epsilon,
)
})?;
table[lhs_index][rhs_index] = *product_index;
}
}
Some(table)
}
pub(super) fn translations_equivalent(
lhs: &Vector3<f64>,
rhs: &Vector3<f64>,
epsilon: f64,
) -> bool {
(lhs - rhs)
.iter()
.all(|&value| (value - value.round()).abs() < epsilon)
}
fn reduce_mod_one(value: f64, epsilon: f64) -> f64 {
let reduced = value.rem_euclid(1.0);
if reduced < epsilon || 1.0 - reduced < epsilon {
0.0
} else {
reduced
}
}