use super::error::FpGroupError;
use super::word::Word;
use crate::group::{Permutation, PermutationGroup};
pub const DEFAULT_MAX_COSETS: usize = 200_000;
pub const MAX_COSET_TABLE_CELLS: usize = 64_000_000;
const MAX_LOOKAHEAD_ROUNDS: usize = 8;
pub fn default_max_cosets(rank: usize) -> usize {
let ceiling = MAX_COSET_TABLE_CELLS / (2 * rank.max(1));
DEFAULT_MAX_COSETS.min(ceiling).max(1)
}
struct Full;
fn column_of(letter: i32) -> usize {
let g = letter.unsigned_abs() as usize - 1;
if letter > 0 {
2 * g
} else {
2 * g + 1
}
}
fn letter_of(column: usize) -> i32 {
let g = (column / 2) as i32 + 1;
if column % 2 == 0 {
g
} else {
-g
}
}
fn columns_of(w: &Word) -> Vec<usize> {
w.letters().iter().map(|&l| column_of(l)).collect()
}
struct Enumerator {
ncols: usize,
table: Vec<usize>,
p: Vec<usize>,
n: usize,
cap: usize,
coincidences: usize,
changes: u64,
}
impl Enumerator {
fn new(rank: usize, cap: usize) -> Enumerator {
let ncols = 2 * rank;
Enumerator {
ncols,
table: vec![0; 2 * ncols],
p: vec![0, 1],
n: 1,
cap,
coincidences: 0,
changes: 0,
}
}
#[inline]
fn get(&self, a: usize, x: usize) -> usize {
self.table[a * self.ncols + x]
}
#[inline]
fn set(&mut self, a: usize, x: usize, v: usize) {
let slot = a * self.ncols + x;
if self.table[slot] != v {
self.table[slot] = v;
self.changes += 1;
}
}
fn rep(&mut self, k: usize) -> usize {
let mut l = k;
while self.p[l] != l {
l = self.p[l];
}
let mut m = k;
while self.p[m] != l {
let next = self.p[m];
self.p[m] = l;
m = next;
}
l
}
#[inline]
fn is_alive(&self, a: usize) -> bool {
self.p[a] == a
}
fn live_count(&self) -> usize {
(1..=self.n).filter(|&a| self.is_alive(a)).count()
}
fn define(&mut self, a: usize, x: usize) -> Result<usize, Full> {
if self.n >= self.cap {
return Err(Full);
}
self.n += 1;
let b = self.n;
self.table.resize(self.table.len() + self.ncols, 0);
self.p.push(b);
self.set(a, x, b);
self.set(b, x ^ 1, a);
Ok(b)
}
fn merge(&mut self, a: usize, b: usize, queue: &mut Vec<usize>) {
let mut a = self.rep(a);
let mut b = self.rep(b);
if a == b {
return;
}
if a > b {
std::mem::swap(&mut a, &mut b);
}
self.p[b] = a;
self.coincidences += 1;
self.changes += 1;
queue.push(b);
}
fn coincidence(&mut self, a: usize, b: usize) {
let mut queue: Vec<usize> = Vec::new();
self.merge(a, b, &mut queue);
let mut head = 0;
while head < queue.len() {
let dead = queue[head];
head += 1;
for x in 0..self.ncols {
let d = self.get(dead, x);
if d == 0 {
continue;
}
self.set(d, x ^ 1, 0);
let mu = self.rep(dead);
let nu = self.rep(d);
let forward = self.get(mu, x);
if forward != 0 {
self.merge(nu, forward, &mut queue);
} else {
let backward = self.get(nu, x ^ 1);
if backward != 0 {
self.merge(mu, backward, &mut queue);
} else {
self.set(mu, x, nu);
self.set(nu, x ^ 1, mu);
}
}
}
}
}
fn scan_and_fill(&mut self, a: usize, w: &[usize]) -> Result<(), Full> {
if w.is_empty() {
return Ok(());
}
let mut f = a;
let mut b = a;
let mut i: isize = 0;
let mut j: isize = w.len() as isize - 1;
loop {
while i <= j {
let t = self.get(f, w[i as usize]);
if t == 0 {
break;
}
f = t;
i += 1;
}
if i > j {
if f != b {
self.coincidence(f, b);
}
return Ok(());
}
while j >= i {
let t = self.get(b, w[j as usize] ^ 1);
if t == 0 {
break;
}
b = t;
j -= 1;
}
if j < i {
self.coincidence(f, b);
return Ok(());
}
if j == i {
let x = w[i as usize];
self.set(f, x, b);
self.set(b, x ^ 1, f);
return Ok(());
}
self.define(f, w[i as usize])?;
}
}
fn scan(&mut self, a: usize, w: &[usize]) -> bool {
if w.is_empty() {
return true;
}
let mut f = a;
let mut b = a;
let mut i: isize = 0;
let mut j: isize = w.len() as isize - 1;
while i <= j {
let t = self.get(f, w[i as usize]);
if t == 0 {
break;
}
f = t;
i += 1;
}
if i > j {
if f != b {
self.coincidence(f, b);
}
return true;
}
while j >= i {
let t = self.get(b, w[j as usize] ^ 1);
if t == 0 {
break;
}
b = t;
j -= 1;
}
if j < i {
self.coincidence(f, b);
return true;
}
if j == i {
let x = w[i as usize];
self.set(f, x, b);
self.set(b, x ^ 1, f);
return true;
}
false
}
fn hlt_pass(&mut self, relators: &[Vec<usize>], subgens: &[Vec<usize>]) -> Result<(), Full> {
for w in subgens {
self.scan_and_fill(1, w)?;
}
let mut a = 1;
while a <= self.n {
if !self.is_alive(a) {
a += 1;
continue;
}
for r in relators {
self.scan_and_fill(a, r)?;
if !self.is_alive(a) {
break;
}
}
if self.is_alive(a) {
for x in 0..self.ncols {
if !self.is_alive(a) {
break;
}
if self.get(a, x) == 0 {
self.define(a, x)?;
}
}
}
a += 1;
}
Ok(())
}
fn lookahead(&mut self, relators: &[Vec<usize>]) {
for a in 1..=self.n {
if !self.is_alive(a) {
continue;
}
for r in relators {
self.scan(a, r);
if !self.is_alive(a) {
break;
}
}
}
}
fn compact(&mut self) {
let mut renumber = vec![0usize; self.n + 1];
let mut m = 0;
for (a, slot) in renumber.iter_mut().enumerate().skip(1) {
if self.p[a] == a {
m += 1;
*slot = m;
}
}
let mut table = vec![0usize; (m + 1) * self.ncols];
for a in 1..=self.n {
if !self.is_alive(a) {
continue;
}
let na = renumber[a];
for x in 0..self.ncols {
let d = self.get(a, x);
if d != 0 {
let rd = self.rep(d);
table[na * self.ncols + x] = renumber[rd];
}
}
}
self.table = table;
self.n = m;
self.p = (0..=m).collect();
}
fn closed(&mut self, relators: &[Vec<usize>], subgens: &[Vec<usize>]) -> Result<bool, Full> {
let before = self.changes;
for w in subgens {
self.scan_and_fill(1, w)?;
}
let mut a = 1;
while a <= self.n {
if self.is_alive(a) {
for r in relators {
self.scan_and_fill(a, r)?;
if !self.is_alive(a) {
break;
}
}
}
a += 1;
}
if self.changes != before {
return Ok(false);
}
for a in 1..=self.n {
if !self.is_alive(a) {
continue;
}
for x in 0..self.ncols {
if self.get(a, x) == 0 {
return Ok(false);
}
}
}
Ok(true)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CosetTable {
rank: usize,
index: usize,
table: Vec<usize>,
subgroup_generators: Vec<Word>,
cosets_defined: usize,
coincidences: usize,
max_cosets: usize,
}
impl CosetTable {
pub fn index(&self) -> usize {
self.index
}
pub fn rank(&self) -> usize {
self.rank
}
pub fn subgroup_generators(&self) -> &[Word] {
&self.subgroup_generators
}
pub fn cosets_defined(&self) -> usize {
self.cosets_defined
}
pub fn coincidences(&self) -> usize {
self.coincidences
}
pub fn max_cosets(&self) -> usize {
self.max_cosets
}
pub fn strategy(&self) -> &'static str {
"HLT with lookahead"
}
pub fn row(&self, coset: usize) -> Result<&[usize], FpGroupError> {
if coset >= self.index {
return Err(FpGroupError::CosetOutOfRange {
coset,
index: self.index,
});
}
let w = 2 * self.rank;
Ok(&self.table[coset * w..coset * w + w])
}
pub fn rows(&self) -> Vec<Vec<usize>> {
let w = 2 * self.rank;
(0..self.index)
.map(|c| self.table[c * w..c * w + w].to_vec())
.collect()
}
pub fn image(&self, coset: usize, letter: i32) -> Result<usize, FpGroupError> {
if coset >= self.index {
return Err(FpGroupError::CosetOutOfRange {
coset,
index: self.index,
});
}
if letter == 0 || letter.unsigned_abs() as usize > self.rank {
return Err(FpGroupError::InvalidGenerator {
letter,
rank: self.rank,
});
}
Ok(self.table[coset * 2 * self.rank + column_of(letter)])
}
pub fn trace(&self, coset: usize, w: &Word) -> Result<usize, FpGroupError> {
let mut c = coset;
if c >= self.index {
return Err(FpGroupError::CosetOutOfRange {
coset,
index: self.index,
});
}
for &l in w.letters() {
c = self.image(c, l)?;
}
Ok(c)
}
pub fn generator_permutation(&self, index: usize) -> Result<Permutation, FpGroupError> {
if index >= self.rank {
return Err(FpGroupError::InvalidGenerator {
letter: index as i32 + 1,
rank: self.rank,
});
}
let w = 2 * self.rank;
let images: Vec<usize> = (0..self.index)
.map(|c| self.table[c * w + 2 * index])
.collect();
Permutation::from_images(images).map_err(FpGroupError::from)
}
pub fn permutations(&self) -> Result<Vec<Permutation>, FpGroupError> {
(0..self.rank)
.map(|i| self.generator_permutation(i))
.collect()
}
pub fn permutation_group(&self) -> Result<PermutationGroup, FpGroupError> {
let perms = self.permutations()?;
PermutationGroup::new(self.index, perms).map_err(FpGroupError::from)
}
pub(super) fn spanning_tree(&self) -> Vec<Option<(usize, usize)>> {
let mut tree: Vec<Option<(usize, usize)>> = vec![None; self.index];
let mut seen = vec![false; self.index];
let mut queue = std::collections::VecDeque::new();
seen[0] = true;
queue.push_back(0usize);
let w = 2 * self.rank;
while let Some(c) = queue.pop_front() {
for x in 0..w {
let d = self.table[c * w + x];
if !seen[d] {
seen[d] = true;
tree[d] = Some((c, x));
queue.push_back(d);
}
}
}
tree
}
pub fn transversal(&self) -> Result<Vec<Word>, FpGroupError> {
let tree = self.spanning_tree();
let mut words: Vec<Option<Word>> = vec![None; self.index];
words[0] = Some(Word::identity());
let mut remaining = self.index - 1;
while remaining > 0 {
let mut progressed = false;
for c in 1..self.index {
if words[c].is_some() {
continue;
}
if let Some((parent, column)) = tree[c] {
if let Some(pw) = words[parent].clone() {
let mut letters = pw.letters().to_vec();
letters.push(letter_of(column));
words[c] = Some(Word::from_letters(&letters)?);
remaining -= 1;
progressed = true;
}
}
}
if !progressed {
break;
}
}
words
.into_iter()
.enumerate()
.map(|(c, w)| {
w.ok_or_else(|| FpGroupError::Internal {
detail: format!("coset {c} is not reachable from H in a complete table"),
})
})
.collect()
}
}
pub fn enumerate(
rank: usize,
relators: &[Word],
subgroup_generators: &[Word],
max_cosets: usize,
) -> Result<CosetTable, FpGroupError> {
if rank == 0 {
return Ok(CosetTable {
rank: 0,
index: 1,
table: Vec::new(),
subgroup_generators: subgroup_generators.to_vec(),
cosets_defined: 1,
coincidences: 0,
max_cosets,
});
}
if max_cosets == 0 {
return Err(FpGroupError::CosetCapTooLarge {
requested: 0,
max: MAX_COSET_TABLE_CELLS / (2 * rank),
});
}
let ceiling = MAX_COSET_TABLE_CELLS / (2 * rank);
if max_cosets > ceiling {
return Err(FpGroupError::CosetCapTooLarge {
requested: max_cosets,
max: ceiling,
});
}
for w in relators.iter().chain(subgroup_generators.iter()) {
if let Some(g) = w.max_generator() {
if g >= rank {
return Err(FpGroupError::InvalidGenerator {
letter: g as i32 + 1,
rank,
});
}
}
}
let rels: Vec<Vec<usize>> = relators
.iter()
.filter(|w| !w.is_empty())
.map(columns_of)
.collect();
let subs: Vec<Vec<usize>> = subgroup_generators
.iter()
.filter(|w| !w.is_empty())
.map(columns_of)
.collect();
let mut e = Enumerator::new(rank, max_cosets);
let mut total_defined = 1usize;
let mut rounds = 0usize;
loop {
let outcome = (|| -> Result<(), Full> {
loop {
e.hlt_pass(&rels, &subs)?;
if e.closed(&rels, &subs)? {
return Ok(());
}
}
})();
total_defined = total_defined.max(e.n);
match outcome {
Ok(()) => break,
Err(Full) => {
rounds += 1;
let before = e.live_count();
e.lookahead(&rels);
e.compact();
let after = e.live_count();
if rounds >= MAX_LOOKAHEAD_ROUNDS || after * 4 >= before * 3 {
return Err(FpGroupError::EnumerationIncomplete {
live_cosets: after,
cosets_defined: total_defined,
max_cosets,
});
}
}
}
}
e.compact();
let index = e.n;
let ncols = e.ncols;
let mut table = vec![0usize; index * ncols];
for c in 0..index {
for x in 0..ncols {
let v = e.get(c + 1, x);
if v == 0 {
return Err(FpGroupError::Internal {
detail: format!("coset table entry ({c}, {x}) undefined after completion"),
});
}
table[c * ncols + x] = v - 1;
}
}
let out = CosetTable {
rank,
index,
table,
subgroup_generators: subgroup_generators.to_vec(),
cosets_defined: total_defined,
coincidences: e.coincidences,
max_cosets,
};
verify(&out, relators, subgroup_generators)?;
Ok(out)
}
fn verify(
t: &CosetTable,
relators: &[Word],
subgroup_generators: &[Word],
) -> Result<(), FpGroupError> {
let ncols = 2 * t.rank;
for c in 0..t.index {
for x in 0..ncols {
let d = t.table[c * ncols + x];
if d >= t.index {
return Err(FpGroupError::Internal {
detail: format!("coset table entry ({c}, {x}) = {d} is out of range"),
});
}
if t.table[d * ncols + (x ^ 1)] != c {
return Err(FpGroupError::Internal {
detail: format!(
"coset table is not consistent: ({c}, {x}) -> {d} but ({d}, {}) does not \
come back",
x ^ 1
),
});
}
}
}
for r in relators {
for c in 0..t.index {
if t.trace(c, r)? != c {
return Err(FpGroupError::Internal {
detail: format!("relator {r} does not close at coset {c}"),
});
}
}
}
for s in subgroup_generators {
if t.trace(0, s)? != 0 {
return Err(FpGroupError::Internal {
detail: format!("subgroup generator {s} does not fix the coset of H"),
});
}
}
Ok(())
}