use super::error::FpGroupError;
use super::presentation::FpGroup;
use super::word::{FreeGroup, Word, MAX_FREE_RANK};
use std::collections::HashSet;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubgroupPresentation {
presentation: FpGroup,
generator_words: Vec<Word>,
index: usize,
}
impl SubgroupPresentation {
pub fn presentation(&self) -> &FpGroup {
&self.presentation
}
pub fn generator_words(&self) -> &[Word] {
&self.generator_words
}
pub fn index(&self) -> usize {
self.index
}
pub fn rank(&self) -> usize {
self.generator_words.len()
}
}
pub fn reidemeister_schreier(
group: &FpGroup,
subgroup_generators: &[Word],
max_cosets: usize,
) -> Result<SubgroupPresentation, FpGroupError> {
let table = group.coset_table_with_cap(subgroup_generators, max_cosets)?;
let index = table.index();
let rank = group.rank();
let tree = table.spanning_tree();
let transversal = table.transversal()?;
let mut generator_of: Vec<Option<usize>> = vec![None; index * rank.max(1)];
let mut generator_words: Vec<Word> = Vec::new();
for c in 0..index {
for i in 0..rank {
let letter = i as i32 + 1;
let d = table.image(c, letter)?;
let forward_tree_edge = tree[d] == Some((c, 2 * i));
let backward_tree_edge = tree[c] == Some((d, 2 * i + 1));
if forward_tree_edge || backward_tree_edge {
continue;
}
let w = transversal[c]
.times(&Word::generator(i)?)
.times(&transversal[d].inverse());
generator_of[c * rank + i] = Some(generator_words.len());
generator_words.push(w);
}
}
if generator_words.len() + (index - 1) != index * rank {
return Err(FpGroupError::Internal {
detail: format!(
"expected {} Schreier generators, built {}",
index * rank - (index - 1),
generator_words.len()
),
});
}
if generator_words.len() > MAX_FREE_RANK {
return Err(FpGroupError::InvalidPresentation {
detail: format!(
"this subgroup needs {} Schreier generators, above MAX_FREE_RANK ({}); \
Reidemeister-Schreier produces index * rank - (index - 1) of them, so the \
index is what has to come down",
generator_words.len(),
MAX_FREE_RANK
),
});
}
let mut relators: Vec<Word> = Vec::new();
let mut seen: HashSet<Word> = HashSet::new();
for c in 0..index {
for r in group.relators() {
let mut letters: Vec<i32> = Vec::new();
let mut a = c;
for &l in r.letters() {
let b = table.image(a, l)?;
let i = l.unsigned_abs() as usize - 1;
if l > 0 {
if let Some(id) = generator_of[a * rank + i] {
letters.push(id as i32 + 1);
}
} else if let Some(id) = generator_of[b * rank + i] {
letters.push(-(id as i32 + 1));
}
a = b;
}
if a != c {
return Err(FpGroupError::Internal {
detail: format!("relator {r} did not close at coset {c} while rewriting"),
});
}
let w = Word::from_letters(&letters)?;
if !w.is_empty() && seen.insert(w.clone()) {
relators.push(w);
}
}
}
let names: Vec<String> = (1..=generator_words.len())
.map(|k| format!("y{k}"))
.collect();
let free = FreeGroup::with_names(&names)?;
let presentation = FpGroup::new(free, relators)?;
Ok(SubgroupPresentation {
presentation,
generator_words,
index,
})
}