use std::collections::HashMap;
use rug::Integer;
use super::element::{
basis_vector, entry_key, first_moved_basis_vector, is_identity, row_vector, EntryKey,
};
use super::error::MatGroupError;
use super::{MAX_MATGROUP_ORBIT, MAX_MATGROUP_SCHREIER_WORK};
use crate::ffield::{FiniteField, GfMatrix};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MatrixSchreierEntry {
pub generator: usize,
pub predecessor: usize,
}
#[derive(Clone, Debug)]
pub struct MatrixOrbit {
base_index: usize,
points: Vec<GfMatrix>,
index: HashMap<EntryKey, usize>,
schreier: Vec<Option<MatrixSchreierEntry>>,
transversal: Vec<GfMatrix>,
transversal_inverse: Vec<GfMatrix>,
}
impl MatrixOrbit {
fn trivial(
field: &FiniteField,
degree: usize,
base_index: usize,
) -> Result<Self, MatGroupError> {
let base = basis_vector(field, degree, base_index)?;
let identity = GfMatrix::identity(field, degree)?;
let mut index = HashMap::new();
index.insert(entry_key(field, &base), 0);
Ok(MatrixOrbit {
base_index,
points: vec![base],
index,
schreier: vec![None],
transversal: vec![identity.clone()],
transversal_inverse: vec![identity],
})
}
pub fn base_index(&self) -> usize {
self.base_index
}
pub fn points(&self) -> &[GfMatrix] {
&self.points
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn schreier_vector(&self) -> &[Option<MatrixSchreierEntry>] {
&self.schreier
}
pub fn transversal_element(&self, i: usize) -> Option<&GfMatrix> {
self.transversal.get(i)
}
pub fn position(&self, field: &FiniteField, v: &GfMatrix) -> Option<usize> {
self.index.get(&entry_key(field, v)).copied()
}
pub fn contains(&self, field: &FiniteField, v: &GfMatrix) -> bool {
self.position(field, v).is_some()
}
}
#[derive(Clone, Debug)]
pub struct MatrixStabilizerLevel {
base_index: usize,
generators: Vec<GfMatrix>,
orbit: MatrixOrbit,
}
impl MatrixStabilizerLevel {
pub fn base_index(&self) -> usize {
self.base_index
}
pub fn generators(&self) -> &[GfMatrix] {
&self.generators
}
pub fn orbit(&self) -> &MatrixOrbit {
&self.orbit
}
}
#[derive(Clone, Debug)]
pub struct MatrixSiftResult {
residue: GfMatrix,
level: usize,
is_member: bool,
}
impl MatrixSiftResult {
pub fn residue(&self) -> &GfMatrix {
&self.residue
}
pub fn level(&self) -> usize {
self.level
}
pub fn is_member(&self) -> bool {
self.is_member
}
}
#[derive(Clone, Debug)]
pub struct MatrixStabilizerChain {
field: FiniteField,
degree: usize,
levels: Vec<MatrixStabilizerLevel>,
work: u64,
}
impl MatrixStabilizerChain {
pub fn field(&self) -> &FiniteField {
&self.field
}
pub fn degree(&self) -> usize {
self.degree
}
pub fn base(&self) -> Vec<usize> {
self.levels.iter().map(|l| l.base_index).collect()
}
pub fn base_vectors(&self) -> Result<Vec<GfMatrix>, MatGroupError> {
self.levels
.iter()
.map(|l| basis_vector(&self.field, self.degree, l.base_index))
.collect()
}
pub fn levels(&self) -> &[MatrixStabilizerLevel] {
&self.levels
}
pub fn work(&self) -> u64 {
self.work
}
pub fn strong_generators(&self) -> Vec<GfMatrix> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for level in &self.levels {
for g in &level.generators {
if seen.insert(entry_key(&self.field, g)) {
out.push(g.clone());
}
}
}
out
}
pub fn order(&self) -> Integer {
let mut acc = Integer::from(1);
for level in &self.levels {
acc *= level.orbit.len();
}
acc
}
pub fn sift(&self, m: &GfMatrix) -> Result<MatrixSiftResult, MatGroupError> {
self.check_shape(m)?;
let (residue, level) = self.strip_from(m, 0)?;
let is_member = is_identity(&self.field, &residue);
Ok(MatrixSiftResult {
residue,
level,
is_member,
})
}
fn check_shape(&self, m: &GfMatrix) -> Result<(), MatGroupError> {
let (rows, cols) = m.shape();
if rows != cols {
return Err(MatGroupError::NotSquare { rows, cols });
}
if rows != self.degree {
return Err(MatGroupError::DegreeMismatch {
expected: self.degree,
got: rows,
});
}
if m.field() != &self.field {
return Err(MatGroupError::FieldMismatch {
expected: format!("{}", self.field),
got: format!("{}", m.field()),
});
}
Ok(())
}
fn strip_from(&self, m: &GfMatrix, start: usize) -> Result<(GfMatrix, usize), MatGroupError> {
let mut h = m.clone();
let mut level = start;
while level < self.levels.len() {
let lev = &self.levels[level];
let image = row_vector(&self.field, &h, lev.base_index)?;
match lev.orbit.position(&self.field, &image) {
None => return Ok((h, level)),
Some(i) => {
h = h.mul(&lev.orbit.transversal_inverse[i])?;
}
}
level += 1;
}
Ok((h, level))
}
pub fn elements_with_cap(&self, cap: u64) -> Result<Vec<GfMatrix>, MatGroupError> {
let order = self.order();
if order > cap {
return Err(MatGroupError::EnumerationTooLarge {
order: order.to_string(),
cap,
});
}
let mut acc = vec![GfMatrix::identity(&self.field, self.degree)?];
for level in &self.levels {
let mut next = Vec::with_capacity(acc.len() * level.orbit.len());
for u in &level.orbit.transversal {
for a in &acc {
next.push(u.mul(a)?);
}
}
acc = next;
}
Ok(acc)
}
}
struct ChainBuilder {
field: FiniteField,
degree: usize,
levels: Vec<MatrixStabilizerLevel>,
work: u64,
budget: u64,
}
impl ChainBuilder {
fn spend(&mut self, n: u64) -> Result<(), MatGroupError> {
self.work = self.work.saturating_add(n);
if self.work > self.budget {
return Err(MatGroupError::WorkBudgetExhausted {
budget: self.budget,
});
}
Ok(())
}
fn push_level(&mut self, g: &GfMatrix) -> Result<(), MatGroupError> {
let base_index =
first_moved_basis_vector(&self.field, g).ok_or(MatGroupError::Internal {
detail: "a non-identity matrix that fixes every standard basis vector",
})?;
if self.levels.iter().any(|l| l.base_index == base_index) {
return Err(MatGroupError::Internal {
detail: "a sifted residue moved a basis vector that is already a base point",
});
}
let orbit = MatrixOrbit::trivial(&self.field, self.degree, base_index)?;
self.levels.push(MatrixStabilizerLevel {
base_index,
generators: Vec::new(),
orbit,
});
Ok(())
}
fn recompute_orbit(&mut self, l: usize) -> Result<(), MatGroupError> {
let base_index = self.levels[l].base_index;
let generators = self.levels[l].generators.clone();
let base = basis_vector(&self.field, self.degree, base_index)?;
let identity = GfMatrix::identity(&self.field, self.degree)?;
let mut points = vec![base.clone()];
let mut index = HashMap::new();
index.insert(entry_key(&self.field, &base), 0usize);
let mut schreier: Vec<Option<MatrixSchreierEntry>> = vec![None];
let mut transversal = vec![identity.clone()];
let mut transversal_inverse = vec![identity];
let mut i = 0;
while i < points.len() {
for (gi, g) in generators.iter().enumerate() {
let image = points[i].mul(g)?;
let key = entry_key(&self.field, &image);
if let std::collections::hash_map::Entry::Vacant(slot) = index.entry(key) {
if points.len() >= MAX_MATGROUP_ORBIT {
return Err(MatGroupError::OrbitTooLarge {
points: points.len() + 1,
max: MAX_MATGROUP_ORBIT,
});
}
let u = transversal[i].mul(g)?;
let u_inverse = u.inverse()?;
slot.insert(points.len());
points.push(image);
schreier.push(Some(MatrixSchreierEntry {
generator: gi,
predecessor: i,
}));
transversal.push(u);
transversal_inverse.push(u_inverse);
}
self.spend(2)?;
}
i += 1;
}
self.levels[l].orbit = MatrixOrbit {
base_index,
points,
index,
schreier,
transversal,
transversal_inverse,
};
Ok(())
}
fn strip_from(&self, m: &GfMatrix, start: usize) -> Result<(GfMatrix, usize), MatGroupError> {
let mut h = m.clone();
let mut level = start;
while level < self.levels.len() {
let lev = &self.levels[level];
let image = row_vector(&self.field, &h, lev.base_index)?;
match lev.orbit.position(&self.field, &image) {
None => return Ok((h, level)),
Some(i) => h = h.mul(&lev.orbit.transversal_inverse[i])?,
}
level += 1;
}
Ok((h, level))
}
fn schreier_sims(&mut self, l: usize) -> Result<(), MatGroupError> {
self.recompute_orbit(l)?;
let mut i = 0;
while i < self.levels[l].orbit.points.len() {
let generator_count = self.levels[l].generators.len();
for gi in 0..generator_count {
let schreier_generator = {
let lev = &self.levels[l];
let x = &lev.generators[gi];
let image = lev.orbit.points[i].mul(x)?;
let j =
lev.orbit
.position(&self.field, &image)
.ok_or(MatGroupError::Internal {
detail: "a generator carried an orbit point outside its own orbit",
})?;
if lev.orbit.schreier[j]
== Some(MatrixSchreierEntry {
generator: gi,
predecessor: i,
})
{
None
} else {
Some(
lev.orbit.transversal[i]
.mul(x)?
.mul(&lev.orbit.transversal_inverse[j])?,
)
}
};
let Some(s) = schreier_generator else {
continue;
};
self.spend(4)?;
if is_identity(&self.field, &s) {
continue;
}
let (residue, j) = self.strip_from(&s, l + 1)?;
self.spend(u64::try_from(self.levels.len()).unwrap_or(u64::MAX))?;
if is_identity(&self.field, &residue) {
continue;
}
if j == self.levels.len() {
self.push_level(&residue)?;
}
for k in (l + 1)..=j {
self.levels[k].generators.push(residue.clone());
}
for k in ((l + 1)..=j).rev() {
self.schreier_sims(k)?;
}
}
i += 1;
}
Ok(())
}
}
pub(crate) fn build_chain(
field: &FiniteField,
degree: usize,
generators: &[GfMatrix],
budget: u64,
) -> Result<MatrixStabilizerChain, MatGroupError> {
let mut builder = ChainBuilder {
field: field.clone(),
degree,
levels: Vec::new(),
work: 0,
budget: budget.max(1),
};
let nontrivial: Vec<GfMatrix> = generators
.iter()
.filter(|g| !is_identity(field, g))
.cloned()
.collect();
if !nontrivial.is_empty() {
builder.push_level(&nontrivial[0])?;
builder.levels[0].generators = nontrivial;
builder.schreier_sims(0)?;
}
Ok(MatrixStabilizerChain {
field: field.clone(),
degree,
levels: builder.levels,
work: builder.work,
})
}
pub(crate) const DEFAULT_BUDGET: u64 = MAX_MATGROUP_SCHREIER_WORK;