use rug::ops::Pow;
use rug::Integer;
use super::element::{
entry_key, field_elements, field_order_u64, is_identity, primitive_element,
projective_normalise,
};
use super::error::MatGroupError;
use super::group::MatGroup;
use super::MAX_MATGROUP_DEGREE;
use crate::ffield::{FieldElement, FiniteField, GfMatrix};
pub fn gl_order(q: &Integer, n: usize) -> Integer {
let qn = q.clone().pow(u32::try_from(n).unwrap_or(u32::MAX));
let mut acc = Integer::from(1);
for i in 0..n {
acc *= qn.clone() - q.clone().pow(u32::try_from(i).unwrap_or(u32::MAX));
}
acc
}
pub fn sl_order(q: &Integer, n: usize) -> Integer {
gl_order(q, n) / (q.clone() - Integer::from(1))
}
pub fn sp_order(q: &Integer, n: usize) -> Integer {
let mut acc = q.clone().pow(u32::try_from(n * n).unwrap_or(u32::MAX));
for i in 1..=n {
acc *= q.clone().pow(u32::try_from(2 * i).unwrap_or(u32::MAX)) - Integer::from(1);
}
acc
}
fn prime_field_basis(field: &FiniteField) -> Result<Vec<FieldElement>, MatGroupError> {
let k = field.degree();
let mut out = Vec::with_capacity(k);
for m in 0..k {
let mut coeffs = vec![0u64; k];
coeffs[m] = 1;
out.push(field.element(&coeffs)?);
}
Ok(out)
}
fn check_degree(family: &'static str, n: usize, max: usize) -> Result<(), MatGroupError> {
if n == 0 {
return Err(MatGroupError::UnsupportedConstruction {
family,
reason: "n must be at least 1",
});
}
if n > max {
return Err(MatGroupError::DegreeTooLarge { degree: n, max });
}
Ok(())
}
fn transvection(
field: &FiniteField,
n: usize,
i: usize,
j: usize,
lambda: &FieldElement,
) -> Result<GfMatrix, MatGroupError> {
let mut entries = vec![field.zero(); n * n];
for d in 0..n {
entries[d * n + d] = field.one();
}
entries[i * n + j] = lambda.clone();
Ok(GfMatrix::from_elements(field, n, n, &entries)?)
}
impl MatGroup {
pub fn special_linear(field: &FiniteField, n: usize) -> Result<MatGroup, MatGroupError> {
check_degree("SL", n, MAX_MATGROUP_DEGREE)?;
if n == 1 {
return MatGroup::trivial(field, 1);
}
let basis = prime_field_basis(field)?;
let mut generators = Vec::with_capacity(n * (n - 1) * basis.len());
for i in 0..n {
for j in 0..n {
if i == j {
continue;
}
for lambda in &basis {
generators.push(transvection(field, n, i, j, lambda)?);
}
}
}
MatGroup::new(field, n, generators)
}
pub fn general_linear(field: &FiniteField, n: usize) -> Result<MatGroup, MatGroupError> {
check_degree("GL", n, MAX_MATGROUP_DEGREE)?;
let a = primitive_element(field)?;
let mut entries = vec![field.zero(); n * n];
for d in 0..n {
entries[d * n + d] = field.one();
}
entries[0] = a;
let scaling = GfMatrix::from_elements(field, n, n, &entries)?;
let mut generators = if n == 1 {
Vec::new()
} else {
MatGroup::special_linear(field, n)?.generators().to_vec()
};
if !is_identity(field, &scaling) {
generators.push(scaling);
}
MatGroup::new(field, n, generators)
}
pub fn symplectic(field: &FiniteField, n: usize) -> Result<MatGroup, MatGroupError> {
if n == 0 {
return Err(MatGroupError::UnsupportedConstruction {
family: "Sp",
reason: "n must be at least 1, giving 2n x 2n matrices",
});
}
let d = 2 * n;
if d > MAX_MATGROUP_DEGREE {
return Err(MatGroupError::DegreeTooLarge {
degree: d,
max: MAX_MATGROUP_DEGREE,
});
}
let omega = symplectic_form_matrix(field, n)?;
let identity = GfMatrix::identity(field, d)?;
let basis = prime_field_basis(field)?;
let alphabet = field_elements(field)?;
let q = alphabet.len();
let total = q.checked_pow(u32::try_from(d).unwrap_or(u32::MAX)).ok_or(
MatGroupError::UnsupportedConstruction {
family: "Sp",
reason: "q^{2n} does not fit in a machine word",
},
)?;
let mut seen = std::collections::HashSet::new();
let mut generators = Vec::new();
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;
}
let v = GfMatrix::from_elements(field, 1, d, &entries)?;
let v = projective_normalise(field, &v)?;
if !seen.insert(entry_key(field, &v)) {
continue;
}
let outer = omega.mul(&v.transpose())?.mul(&v)?;
for lambda in &basis {
let t = identity.add(&outer.scalar_mul(lambda)?)?;
if !is_identity(field, &t) {
generators.push(t);
}
}
}
MatGroup::new(field, d, generators)
}
pub fn singer_cycle(field: &FiniteField, n: usize) -> Result<MatGroup, MatGroupError> {
check_degree("Singer", n, MAX_MATGROUP_DEGREE)?;
let q = field_order_u64(field)?;
if n == 1 {
let a = primitive_element(field)?;
let m = GfMatrix::from_elements(field, 1, 1, std::slice::from_ref(&a))?;
return MatGroup::new(field, 1, vec![m]);
}
if !field.is_prime_field() {
return Err(MatGroupError::UnsupportedConstruction {
family: "Singer",
reason: "a primitive polynomial of degree n is searched for over GF(p) only; \
over a proper extension GF(p^k) this needs irreducibility testing \
that the GF(q) layer does not expose",
});
}
let p = field.characteristic();
let big_q = Integer::from(q).pow(u32::try_from(n).unwrap_or(u32::MAX));
let candidates = big_q
.to_u64()
.filter(|c| *c <= super::MAX_MATGROUP_FIELD_ORDER)
.ok_or_else(|| MatGroupError::FieldTooLarge {
order: big_q.to_string(),
max: super::MAX_MATGROUP_FIELD_ORDER,
})?;
let order_u64 = candidates - 1;
let primes = distinct_prime_factors(order_u64);
for code in 0..candidates {
let mut rest = code;
let mut coeffs = Vec::with_capacity(n + 1);
for _ in 0..n {
coeffs.push(rest % q);
rest /= q;
}
if coeffs[0] == 0 {
continue;
}
coeffs.push(1);
if FiniteField::with_defining_polynomial(p, &coeffs).is_err() {
continue;
}
let companion = companion_matrix(field, &coeffs)?;
if is_primitive(field, &companion, order_u64, &primes)? {
return MatGroup::new(field, n, vec![companion]);
}
}
Err(MatGroupError::Internal {
detail: "no primitive polynomial of degree n over GF(p) was found, but one exists",
})
}
}
fn symplectic_form_matrix(field: &FiniteField, n: usize) -> Result<GfMatrix, MatGroupError> {
let d = 2 * n;
let minus_one = field.characteristic() - 1;
let mut entries = vec![0u64; d * d];
for i in 0..n {
entries[i * d + (n + i)] = 1;
entries[(n + i) * d + i] = minus_one;
}
Ok(GfMatrix::from_u64(field, d, d, &entries)?)
}
fn companion_matrix(field: &FiniteField, coeffs: &[u64]) -> Result<GfMatrix, MatGroupError> {
let n = coeffs.len() - 1;
let p = field.characteristic();
let mut entries = vec![0u64; n * n];
for i in 0..n - 1 {
entries[i * n + (i + 1)] = 1;
}
for (j, &c) in coeffs.iter().take(n).enumerate() {
entries[(n - 1) * n + j] = (p - c % p) % p;
}
Ok(GfMatrix::from_u64(field, n, n, &entries)?)
}
fn matrix_pow(field: &FiniteField, m: &GfMatrix, exponent: u64) -> Result<GfMatrix, MatGroupError> {
let mut result = GfMatrix::identity(field, m.nrows())?;
let mut base = m.clone();
let mut e = exponent;
while e > 0 {
if e & 1 == 1 {
result = result.mul(&base)?;
}
e >>= 1;
if e > 0 {
base = base.mul(&base)?;
}
}
Ok(result)
}
fn is_primitive(
field: &FiniteField,
m: &GfMatrix,
order: u64,
primes: &[u64],
) -> Result<bool, MatGroupError> {
if !is_identity(field, &matrix_pow(field, m, order)?) {
return Ok(false);
}
for &r in primes {
if is_identity(field, &matrix_pow(field, m, order / r)?) {
return Ok(false);
}
}
Ok(true)
}
fn distinct_prime_factors(mut n: u64) -> Vec<u64> {
let mut out = Vec::new();
let mut d = 2u64;
while d.saturating_mul(d) <= n {
if n % d == 0 {
out.push(d);
while n % d == 0 {
n /= d;
}
}
d += 1;
}
if n > 1 {
out.push(n);
}
out
}