use std::cmp::Ordering;
use rug::Integer;
use super::error::CharacterError;
use crate::group::{Permutation, PermutationGroup};
pub const DEFAULT_CLASS_ENUMERATION_CAP: u64 = 20_000;
pub const MAX_CLASS_ENUMERATION_CAP: u64 = 200_000;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConjugacyClass {
representative: Permutation,
size: u64,
centraliser_order: Integer,
element_order: u64,
}
impl ConjugacyClass {
pub fn representative(&self) -> &Permutation {
&self.representative
}
pub fn size(&self) -> u64 {
self.size
}
pub fn centraliser_order(&self) -> &Integer {
&self.centraliser_order
}
pub fn element_order(&self) -> u64 {
self.element_order
}
}
#[derive(Clone, Debug)]
pub struct ConjugacyClasses {
degree: usize,
group_order: Integer,
exponent: u64,
classes: Vec<ConjugacyClass>,
elements: Vec<Permutation>,
class_of: Vec<usize>,
members: Vec<Vec<usize>>,
inverse_class: Vec<usize>,
}
impl ConjugacyClasses {
pub fn of(group: &PermutationGroup) -> Result<Self, CharacterError> {
Self::of_with_cap(group, DEFAULT_CLASS_ENUMERATION_CAP)
}
pub fn of_with_cap(group: &PermutationGroup, cap: u64) -> Result<Self, CharacterError> {
if cap > MAX_CLASS_ENUMERATION_CAP {
return Err(CharacterError::CapTooLarge {
cap,
max: MAX_CLASS_ENUMERATION_CAP,
});
}
let group_order = group.order()?;
if group_order > cap {
return Err(CharacterError::OrderTooLarge {
order: group_order.to_string(),
cap,
});
}
let degree = group.degree();
let mut elements = group.elements_with_cap(cap)?;
elements.sort_unstable();
elements.dedup();
if group_order != elements.len() {
return Err(CharacterError::Internal {
detail: format!(
"element enumeration produced {} distinct elements for a group of order {}",
elements.len(),
group_order
),
});
}
let inverse_generators: Vec<Permutation> =
group.generators().iter().map(|g| g.inverse()).collect();
let unassigned = usize::MAX;
let mut class_of = vec![unassigned; elements.len()];
let mut raw_members: Vec<Vec<usize>> = Vec::new();
for seed in 0..elements.len() {
if class_of[seed] != unassigned {
continue;
}
let label = raw_members.len();
let mut orbit = vec![seed];
class_of[seed] = label;
let mut frontier = 0usize;
while frontier < orbit.len() {
let current = elements[orbit[frontier]].clone();
frontier += 1;
for (g, g_inv) in group.generators().iter().zip(&inverse_generators) {
let conjugate = g_inv.compose(¤t)?.compose(g)?;
let position = Self::locate(&elements, &conjugate).ok_or_else(|| {
CharacterError::Internal {
detail: format!(
"the conjugate {conjugate} of a group element is not in the \
element list"
),
}
})?;
if class_of[position] == unassigned {
class_of[position] = label;
orbit.push(position);
}
}
}
raw_members.push(orbit);
}
let mut summaries: Vec<(u64, u64, usize, usize)> = Vec::with_capacity(raw_members.len());
for (label, orbit) in raw_members.iter().enumerate() {
let rep_index = *orbit.iter().min().expect("an orbit is never empty");
let order = to_u64(&elements[rep_index].order(), "an element order")?;
summaries.push((order, orbit.len() as u64, rep_index, label));
}
summaries.sort_by(|a, b| match a.0.cmp(&b.0) {
Ordering::Equal => match a.1.cmp(&b.1) {
Ordering::Equal => elements[a.2].cmp(&elements[b.2]),
other => other,
},
other => other,
});
let mut classes = Vec::with_capacity(summaries.len());
let mut members: Vec<Vec<usize>> = Vec::with_capacity(summaries.len());
let mut relabel = vec![0usize; raw_members.len()];
let mut exponent = Integer::from(1);
for (new_label, &(order, size, rep_index, old_label)) in summaries.iter().enumerate() {
relabel[old_label] = new_label;
if !group_order.is_divisible(&Integer::from(size)) {
return Err(CharacterError::SelfCheckFailed {
check: "class sizes",
detail: format!(
"class {new_label} has size {size}, which does not divide |G| = \
{group_order}"
),
});
}
classes.push(ConjugacyClass {
representative: elements[rep_index].clone(),
size,
centraliser_order: Integer::from(&group_order) / Integer::from(size),
element_order: order,
});
members.push(Vec::new());
exponent.lcm_mut(&Integer::from(order));
}
for label in class_of.iter_mut() {
*label = relabel[*label];
}
for (new_label, &(_, _, _, old_label)) in summaries.iter().enumerate() {
members[new_label] = std::mem::take(&mut raw_members[old_label]);
}
let total = classes
.iter()
.fold(Integer::new(), |acc, c| acc + Integer::from(c.size));
if total != group_order {
return Err(CharacterError::SelfCheckFailed {
check: "class sizes",
detail: format!("the class sizes sum to {total}, not to |G| = {group_order}"),
});
}
let exponent = to_u64(&exponent, "exp G")?;
let mut inverse_class = vec![0usize; classes.len()];
for (i, class) in classes.iter().enumerate() {
let inverse = class.representative.inverse();
let position =
Self::locate(&elements, &inverse).ok_or_else(|| CharacterError::Internal {
detail: format!("the inverse {inverse} of a group element is not in the list"),
})?;
inverse_class[i] = class_of[position];
}
Ok(ConjugacyClasses {
degree,
group_order,
exponent,
classes,
elements,
class_of,
members,
inverse_class,
})
}
fn locate(elements: &[Permutation], target: &Permutation) -> Option<usize> {
elements.binary_search(target).ok()
}
pub fn degree(&self) -> usize {
self.degree
}
pub fn group_order(&self) -> &Integer {
&self.group_order
}
pub fn exponent(&self) -> u64 {
self.exponent
}
pub fn len(&self) -> usize {
self.classes.len()
}
pub fn is_empty(&self) -> bool {
self.classes.is_empty()
}
pub fn classes(&self) -> &[ConjugacyClass] {
&self.classes
}
pub fn class(&self, index: usize) -> Result<&ConjugacyClass, CharacterError> {
self.classes
.get(index)
.ok_or(CharacterError::ClassIndexOutOfRange {
index,
classes: self.classes.len(),
})
}
pub fn class_of(&self, element: &Permutation) -> Result<usize, CharacterError> {
if element.degree() != self.degree {
return Err(CharacterError::Group(
crate::group::GroupError::DegreeMismatch {
left: self.degree,
right: element.degree(),
},
));
}
match Self::locate(&self.elements, element) {
Some(position) => Ok(self.class_of[position]),
None => Err(CharacterError::NotAMember {
element: element.to_string(),
}),
}
}
pub fn inverse_class(&self, index: usize) -> Result<usize, CharacterError> {
self.inverse_class
.get(index)
.copied()
.ok_or(CharacterError::ClassIndexOutOfRange {
index,
classes: self.classes.len(),
})
}
pub fn class_elements(&self, index: usize) -> Result<Vec<Permutation>, CharacterError> {
let members = self
.members
.get(index)
.ok_or(CharacterError::ClassIndexOutOfRange {
index,
classes: self.classes.len(),
})?;
let mut out: Vec<Permutation> = members.iter().map(|&i| self.elements[i].clone()).collect();
out.sort_unstable();
Ok(out)
}
pub fn multiplication_coefficient(
&self,
i: usize,
j: usize,
k: usize,
) -> Result<u64, CharacterError> {
let n = self.classes.len();
for &index in &[i, j, k] {
if index >= n {
return Err(CharacterError::ClassIndexOutOfRange { index, classes: n });
}
}
let target = &self.classes[k].representative;
let mut count = 0u64;
for &member in &self.members[i] {
let x = &self.elements[member];
let y = x.inverse().compose(target)?;
let position =
Self::locate(&self.elements, &y).ok_or_else(|| CharacterError::Internal {
detail: format!("the product {y} of two group elements left the group"),
})?;
if self.class_of[position] == j {
count += 1;
}
}
Ok(count)
}
pub fn multiplication_matrix(&self, k: usize) -> Result<Vec<Vec<u64>>, CharacterError> {
let n = self.classes.len();
if k >= n {
return Err(CharacterError::ClassIndexOutOfRange {
index: k,
classes: n,
});
}
let mut matrix = vec![vec![0u64; n]; n];
for (j, class) in self.classes.iter().enumerate() {
let target = &class.representative;
for &member in &self.members[k] {
let u = &self.elements[member];
let v = u.inverse().compose(target)?;
let position =
Self::locate(&self.elements, &v).ok_or_else(|| CharacterError::Internal {
detail: format!("the product {v} of two group elements left the group"),
})?;
matrix[self.class_of[position]][j] += 1;
}
}
Ok(matrix)
}
}
pub(super) fn to_u64(value: &Integer, what: &str) -> Result<u64, CharacterError> {
value.to_u64().ok_or_else(|| CharacterError::Internal {
detail: format!("{what} = {value} does not fit a 64-bit word"),
})
}