use rug::ops::Pow;
use rug::Integer;
use super::symplectic::is_symplectic;
use super::StabilizerError;
use crate::ffield::{FieldElement, FiniteField, GfMatrix};
use crate::group::{Permutation, PermutationGroup};
pub const MAX_MATRIX_ENUMERATION: u64 = 1 << 20;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MatrixGroupKind {
GeneralLinear,
SpecialLinear,
Symplectic,
}
impl MatrixGroupKind {
fn name(&self) -> &'static str {
match self {
MatrixGroupKind::GeneralLinear => "GL",
MatrixGroupKind::SpecialLinear => "SL",
MatrixGroupKind::Symplectic => "Sp",
}
}
}
#[derive(Clone, Debug)]
pub struct MatrixGroup {
kind: MatrixGroupKind,
field: FiniteField,
degree: usize,
}
impl MatrixGroup {
pub fn general_linear(field: &FiniteField, n: usize) -> Result<Self, StabilizerError> {
Self::linear(MatrixGroupKind::GeneralLinear, field, n)
}
pub fn special_linear(field: &FiniteField, n: usize) -> Result<Self, StabilizerError> {
Self::linear(MatrixGroupKind::SpecialLinear, field, n)
}
fn linear(
kind: MatrixGroupKind,
field: &FiniteField,
n: usize,
) -> Result<Self, StabilizerError> {
if n == 0 {
return Err(StabilizerError::ShapeMismatch {
op: "build a linear group",
expected: "n ≥ 1".to_string(),
got: "n = 0".to_string(),
});
}
Ok(Self {
kind,
field: field.clone(),
degree: n,
})
}
pub fn symplectic(field: &FiniteField, n: usize) -> Result<Self, StabilizerError> {
if n == 0 {
return Err(StabilizerError::ShapeMismatch {
op: "build a symplectic group",
expected: "n ≥ 1, giving 2n × 2n matrices".to_string(),
got: "n = 0".to_string(),
});
}
Ok(Self {
kind: MatrixGroupKind::Symplectic,
field: field.clone(),
degree: 2 * n,
})
}
pub fn kind(&self) -> MatrixGroupKind {
self.kind
}
pub fn field(&self) -> &FiniteField {
&self.field
}
pub fn degree(&self) -> usize {
self.degree
}
pub fn field_order(&self) -> Integer {
Integer::from(self.field.characteristic()).pow(self.field.degree() as u32)
}
pub fn order(&self) -> Integer {
let q = self.field_order();
match self.kind {
MatrixGroupKind::GeneralLinear => gl_order(&q, self.degree),
MatrixGroupKind::SpecialLinear => {
let full = gl_order(&q, self.degree);
full / (q - Integer::from(1))
}
MatrixGroupKind::Symplectic => {
let n = self.degree / 2;
let mut acc = q.clone().pow((n * n) as u32);
for i in 1..=n {
acc *= q.clone().pow((2 * i) as u32) - Integer::from(1);
}
acc
}
}
}
pub fn contains(&self, m: &GfMatrix) -> Result<bool, StabilizerError> {
if m.field() != &self.field {
return Err(StabilizerError::FieldMismatch {
lhs: format!("{}", self.field),
rhs: format!("{}", m.field()),
});
}
let (r, c) = m.shape();
if r != self.degree || c != self.degree {
return Err(StabilizerError::ShapeMismatch {
op: "matrix-group membership",
expected: format!("a {}x{} matrix", self.degree, self.degree),
got: format!("{r}x{c}"),
});
}
match self.kind {
MatrixGroupKind::GeneralLinear => Ok(!m.determinant()?.is_zero()),
MatrixGroupKind::SpecialLinear => Ok(m.determinant()? == self.field.one()),
MatrixGroupKind::Symplectic => is_symplectic(m),
}
}
pub fn elements(&self) -> Result<Vec<GfMatrix>, StabilizerError> {
self.elements_with_cap(MAX_MATRIX_ENUMERATION)
}
pub fn elements_with_cap(&self, cap: u64) -> Result<Vec<GfMatrix>, StabilizerError> {
let q = self.field_order();
let cells = self.degree * self.degree;
let candidates = q.clone().pow(cells as u32);
if candidates > cap {
return Err(StabilizerError::EnumerationTooLarge {
candidates: candidates.to_string(),
cap,
});
}
let total = candidates
.to_u64()
.ok_or(StabilizerError::EnumerationTooLarge {
candidates: candidates.to_string(),
cap,
})?;
let alphabet = field_elements(&self.field)?;
let qs = alphabet.len() as u64;
let mut out = Vec::new();
let mut entries = vec![self.field.zero(); cells];
for code in 0..total {
let mut rest = code;
for entry in entries.iter_mut() {
*entry = alphabet[(rest % qs) as usize].clone();
rest /= qs;
}
let m = GfMatrix::from_elements(&self.field, self.degree, self.degree, &entries)?;
if self.contains(&m)? {
out.push(m);
}
}
Ok(out)
}
pub fn permutation_action(&self) -> Result<PermutationGroup, StabilizerError> {
let elements = self.elements()?;
let alphabet = field_elements(&self.field)?;
let q = alphabet.len();
let points = q.pow(self.degree as u32) - 1;
let vectors = nonzero_vectors(&self.field, self.degree, &alphabet)?;
let target = self.order();
let mut gens: Vec<Permutation> = Vec::new();
let mut group = perm_group(points, &gens)?;
for m in &elements {
let p = induced_permutation(&self.field, m, &vectors, q, self.degree)?;
if !gens.is_empty() && group.contains(&p).map_err(group_err)? {
continue;
}
if gens.is_empty() && p.is_identity() {
continue;
}
gens.push(p);
group = perm_group(points, &gens)?;
if group.order().map_err(group_err)? == target {
break;
}
}
Ok(group)
}
}
fn group_err(e: crate::group::GroupError) -> StabilizerError {
StabilizerError::InconsistentResult {
reason: format!("the permutation layer refused: {e}"),
}
}
fn perm_group(points: usize, gens: &[Permutation]) -> Result<PermutationGroup, StabilizerError> {
PermutationGroup::new(points, gens.to_vec()).map_err(group_err)
}
fn gl_order(q: &Integer, n: usize) -> Integer {
let qn = q.clone().pow(n as u32);
let mut acc = Integer::from(1);
for i in 0..n {
acc *= qn.clone() - q.clone().pow(i as u32);
}
acc
}
fn field_elements(field: &FiniteField) -> Result<Vec<FieldElement>, StabilizerError> {
let p = field.characteristic();
let k = field.degree();
let q = field
.order()
.and_then(|o| usize::try_from(o).ok())
.ok_or_else(|| StabilizerError::EnumerationTooLarge {
candidates: format!("GF({p}^{k})"),
cap: MAX_MATRIX_ENUMERATION,
})?;
let mut out = Vec::with_capacity(q);
for code in 0..q {
let mut rest = code as u64;
let mut coeffs = Vec::with_capacity(k);
for _ in 0..k {
coeffs.push(rest % p);
rest /= p;
}
out.push(field.element(&coeffs)?);
}
Ok(out)
}
fn nonzero_vectors(
field: &FiniteField,
d: usize,
alphabet: &[FieldElement],
) -> Result<Vec<GfMatrix>, StabilizerError> {
let q = alphabet.len();
let total = q.pow(d as u32);
let mut out = Vec::with_capacity(total - 1);
for code in 1..total {
let mut rest = code;
let mut entries = Vec::with_capacity(d);
for _ in 0..d {
entries.push(alphabet[rest % q].clone());
rest /= q;
}
out.push(GfMatrix::from_elements(field, 1, d, &entries)?);
}
Ok(out)
}
fn vector_index(field: &FiniteField, v: &GfMatrix, q: usize, d: usize) -> usize {
let k = field.degree();
let p = field.characteristic() as usize;
let mut idx = 0usize;
let mut scale = 1usize;
for j in 0..d {
let e = v.entry(0, j).unwrap_or_else(|_| field.zero());
let digits = e.coefficients_padded(k);
let mut val = 0usize;
let mut ps = 1usize;
for c in digits {
val += (c as usize) * ps;
ps *= p;
}
idx += val * scale;
scale *= q;
}
idx - 1
}
fn induced_permutation(
field: &FiniteField,
m: &GfMatrix,
vectors: &[GfMatrix],
q: usize,
d: usize,
) -> Result<Permutation, StabilizerError> {
let mut images = Vec::with_capacity(vectors.len());
for v in vectors {
let w = v.mul(m)?;
images.push(vector_index(field, &w, q, d));
}
Permutation::from_images(images).map_err(group_err)
}
impl std::fmt::Display for MatrixGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let q = self.field_order();
match self.kind {
MatrixGroupKind::Symplectic => {
write!(f, "Sp({}, {})", self.degree, q)
}
k => write!(f, "{}({}, {})", k.name(), self.degree, q),
}
}
}