use std::collections::HashSet;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Permutation {
mapping: Vec<usize>,
}
impl Permutation {
pub fn new(mapping: Vec<usize>) -> Self {
assert!(
is_valid_permutation(&mapping),
"Provided vector is not a valid permutation."
);
Permutation { mapping }
}
pub fn len(&self) -> usize {
self.mapping.len()
}
pub fn is_empty(&self) -> bool {
self.mapping.is_empty()
}
pub fn apply(&self, x: usize) -> usize {
self.mapping[x]
}
pub fn compose(&self, other: &Self) -> Self {
assert_eq!(
self.len(),
other.len(),
"Cannot compose permutations of different sizes."
);
let n = self.len();
let mut new_map = vec![0; n];
for (i, item) in new_map.iter_mut().enumerate().take(n) {
*item = self.mapping[other.mapping[i]];
}
Permutation::new(new_map)
}
pub fn inverse(&self) -> Self {
let n = self.len();
let mut inv = vec![0; n];
for i in 0..n {
inv[self.mapping[i]] = i;
}
Permutation::new(inv)
}
pub fn identity(n: usize) -> Self {
Permutation::new((0..n).collect())
}
}
fn is_valid_permutation(mapping: &[usize]) -> bool {
let n = mapping.len();
let mut seen = vec![false; n];
for &m in mapping {
if m >= n || seen[m] {
return false;
}
seen[m] = true;
}
true
}
#[derive(Clone, Debug)]
pub struct PermutationGroup {
elements: Vec<Permutation>,
n: usize,
}
impl PermutationGroup {
pub fn from_generators(generators: &[Permutation]) -> Self {
assert!(
!generators.is_empty(),
"Must provide at least one generator."
);
let n = generators[0].len();
for g in generators {
assert_eq!(g.len(), n, "Generators must have uniform permutation size.");
}
let mut group_elems = HashSet::new();
let id = Permutation::identity(n);
group_elems.insert(id);
for gen in generators {
group_elems.insert(gen.clone());
}
let mut queue: Vec<Permutation> = group_elems.iter().cloned().collect();
let mut idx = 0;
while idx < queue.len() {
let current = queue[idx].clone();
idx += 1;
for g in generators {
let comp = current.compose(g);
if group_elems.insert(comp.clone()) {
queue.push(comp);
}
}
let known_elems: Vec<_> = group_elems.iter().cloned().collect();
for e in known_elems {
let comp = current.compose(&e);
if group_elems.insert(comp.clone()) {
queue.push(comp);
}
}
}
PermutationGroup {
elements: group_elems.into_iter().collect(),
n,
}
}
pub fn order(&self) -> usize {
self.elements.len()
}
pub fn contains(&self, p: &Permutation) -> bool {
self.elements.contains(p)
}
pub fn identity(&self) -> Option<Permutation> {
self.elements.iter().find(|perm| is_identity(perm)).cloned()
}
pub fn all_subgroups(&self) -> Vec<PermutationGroup> {
let all_elems: Vec<Permutation> = self.elements.clone();
let mut subgroups = Vec::new();
let total = 1 << self.order();
'outer: for mask in 0..total {
let mut subset = Vec::new();
for (i, elem) in all_elems.iter().enumerate().take(self.order()) {
if (mask & (1 << i)) != 0 {
subset.push(elem.clone());
}
}
if subset.is_empty() {
continue; }
if !subset.iter().any(is_identity) {
continue;
}
for a in &subset {
for b in &subset {
let ab = a.compose(b);
if !subset.contains(&ab) {
continue 'outer;
}
let a_inv = a.inverse();
if !subset.contains(&a_inv) {
continue 'outer;
}
}
}
let sub = PermutationGroup {
elements: subset,
n: self.n,
};
subgroups.push(sub);
}
subgroups
}
pub fn is_normal_in(&self, parent: &PermutationGroup) -> bool {
if !self.is_subgroup_of(parent) {
return false;
}
for g in &parent.elements {
let g_inv = g.inverse();
for x in &self.elements {
let gxg = g.compose(x).compose(&g_inv);
if !self.contains(&gxg) {
return false;
}
}
}
true
}
pub fn is_subgroup_of(&self, parent: &PermutationGroup) -> bool {
for e in &self.elements {
if !parent.contains(e) {
return false;
}
}
true
}
}
fn is_identity(p: &Permutation) -> bool {
for (i, &m) in p.mapping.iter().enumerate() {
if i != m {
return false;
}
}
true
}
pub fn find_sylow_p_subgroup(group: &PermutationGroup, p: usize) -> Option<PermutationGroup> {
let order = group.order();
let mut p_power = 1;
while order % (p_power * p) == 0 {
p_power *= p;
}
group
.all_subgroups()
.into_iter()
.find(|sub| sub.order() == p_power)
}
pub fn find_complement(g: &PermutationGroup, n: &PermutationGroup) -> Option<PermutationGroup> {
let target_order = g.order() / n.order();
'subgroup_search: for h_sub in g.all_subgroups() {
if h_sub.order() == target_order {
for x in &h_sub.elements {
if !is_identity(x) && n.contains(x) {
continue 'subgroup_search;
}
}
if covers_whole_group(g, n, &h_sub) {
return Some(h_sub);
}
}
}
None
}
fn covers_whole_group(g: &PermutationGroup, n: &PermutationGroup, h: &PermutationGroup) -> bool {
'outer: for x in &g.elements {
for nn in &n.elements {
for hh in &h.elements {
let comp = nn.compose(hh);
if &comp == x {
continue 'outer;
}
}
}
return false; }
true
}
pub fn schur_zassenhaus(
group: &PermutationGroup,
p: usize,
) -> Option<(PermutationGroup, PermutationGroup)> {
if group.order() == 1 {
return None;
}
let n_sub = find_sylow_p_subgroup(group, p)?;
if !n_sub.is_normal_in(group) {
return None;
}
if group.order() % n_sub.order() != 0 {
return None;
}
let h_sub = find_complement(group, &n_sub)?;
Some((n_sub, h_sub))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_s3_schur_zassenhaus_p3() {
let p1 = Permutation::new(vec![1, 0, 2]); let p2 = Permutation::new(vec![0, 2, 1]); let s3 = PermutationGroup::from_generators(&[p1, p2]);
let factor = schur_zassenhaus(&s3, 3);
assert!(factor.is_some());
let (n_sub, h_sub) = factor.unwrap();
assert_eq!(n_sub.order(), 3);
assert_eq!(h_sub.order(), 2);
let mut intersection_size = 0;
for x in &n_sub.elements {
if h_sub.contains(x) {
intersection_size += 1;
}
}
assert_eq!(intersection_size, 1);
}
#[test]
fn test_s3_schur_zassenhaus_p2() {
let p1 = Permutation::new(vec![1, 0, 2]); let p2 = Permutation::new(vec![0, 2, 1]); let s3 = PermutationGroup::from_generators(&[p1, p2]);
let factor = schur_zassenhaus(&s3, 2);
assert!(factor.is_none());
}
#[test]
fn test_trivial() {
let trivial = Permutation::new(vec![0]);
let g = PermutationGroup::from_generators(&[trivial]);
assert!(schur_zassenhaus(&g, 2).is_none());
assert!(schur_zassenhaus(&g, 3).is_none());
}
}