#![allow(clippy::needless_range_loop)]
use super::abelian::{preimage_lattice_blocked, quotient_invariants, AbelianInvariants};
use super::error::FpGroupError;
use super::presentation::FpGroup;
use rug::Integer;
pub const MAX_MODULE_RANK: usize = 8;
pub const MAX_COHOMOLOGY_GROUP_ORDER: usize = 32;
pub const MAX_COCHAIN_DIMENSION: usize = 2000;
pub const MAX_COHOMOLOGY_DEGREE: usize = 2;
const MAX_ACTION_ENTRY_BITS: u32 = 20;
const MAX_DERIVED_ENTRY_BITS: u32 = 128;
type Mat = Vec<Vec<Integer>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GModule {
invariants: Vec<Integer>,
action: Vec<Mat>,
}
impl GModule {
pub fn trivial(invariants: Vec<Integer>, group_rank: usize) -> Result<GModule, FpGroupError> {
let rank = invariants.len();
let identity: Mat = (0..rank)
.map(|i| {
(0..rank)
.map(|j| Integer::from(usize::from(i == j)))
.collect()
})
.collect();
GModule::new(invariants, vec![identity; group_rank])
}
pub fn new(invariants: Vec<Integer>, action: Vec<Mat>) -> Result<GModule, FpGroupError> {
let rank = invariants.len();
if rank == 0 {
return Err(FpGroupError::ModuleShape {
detail: "a module needs at least one generator".into(),
});
}
if rank > MAX_MODULE_RANK {
return Err(FpGroupError::ModuleShape {
detail: format!("rank {rank} exceeds MAX_MODULE_RANK ({MAX_MODULE_RANK})"),
});
}
for (i, d) in invariants.iter().enumerate() {
if *d < 0 {
return Err(FpGroupError::ModuleShape {
detail: format!(
"invariant {i} is {d}; use 0 for a Z summand and d >= 1 for Z/d"
),
});
}
}
for (k, m) in action.iter().enumerate() {
if m.len() != rank {
return Err(FpGroupError::ModuleShape {
detail: format!("action matrix {k} has {} rows, expected {rank}", m.len()),
});
}
for (i, row) in m.iter().enumerate() {
if row.len() != rank {
return Err(FpGroupError::ModuleShape {
detail: format!(
"row {i} of action matrix {k} has {} entries, expected {rank}",
row.len()
),
});
}
for e in row {
if e.significant_bits() > MAX_ACTION_ENTRY_BITS {
return Err(FpGroupError::ModuleShape {
detail: format!(
"action matrix {k} has an entry of {} bits, above the {MAX_ACTION_ENTRY_BITS}-bit limit",
e.significant_bits()
),
});
}
}
}
check_preserves_lattice(m, &invariants, k)?;
}
Ok(GModule { invariants, action })
}
pub fn rank(&self) -> usize {
self.invariants.len()
}
pub fn invariants(&self) -> &[Integer] {
&self.invariants
}
pub fn group_rank(&self) -> usize {
self.action.len()
}
pub fn generator_action(&self, k: usize) -> Option<&Mat> {
self.action.get(k)
}
pub fn is_trivial_action(&self) -> bool {
let identity = identity_matrix(self.rank());
self.action.iter().all(|m| *m == identity)
}
}
fn check_preserves_lattice(
m: &Mat,
invariants: &[Integer],
which: usize,
) -> Result<(), FpGroupError> {
let rank = invariants.len();
for j in 0..rank {
if invariants[j] == 0 {
continue;
}
for i in 0..rank {
let product = Integer::from(&invariants[j] * &m[i][j]);
let ok = if invariants[i] == 0 {
product == 0
} else {
product.is_divisible(&invariants[i])
};
if !ok {
return Err(FpGroupError::ActionNotWellDefined {
detail: format!(
"generator {which}'s matrix does not respect the module's relations: \
entry ({i},{j}) is {} and {} * that is not 0 in Z/{}",
m[i][j], invariants[j], invariants[i]
),
});
}
}
}
Ok(())
}
fn reduce(m: &Mat, invariants: &[Integer]) -> Mat {
m.iter()
.enumerate()
.map(|(i, row)| {
if invariants[i] > 0 {
row.iter()
.map(|e| e.clone().modulo(&invariants[i]))
.collect()
} else {
row.clone()
}
})
.collect()
}
fn mat_mul(a: &Mat, b: &Mat) -> Mat {
let n = a.len();
let mut out = vec![vec![Integer::from(0); n]; n];
for i in 0..n {
for k in 0..n {
if a[i][k] == 0 {
continue;
}
for j in 0..n {
if b[k][j] == 0 {
continue;
}
let term = Integer::from(&a[i][k] * &b[k][j]);
out[i][j] += term;
}
}
}
out
}
fn identity_matrix(rank: usize) -> Mat {
(0..rank)
.map(|i| {
(0..rank)
.map(|j| Integer::from(usize::from(i == j)))
.collect()
})
.collect()
}
fn element_actions(right_mult: &[Vec<usize>], module: &GModule) -> Result<Vec<Mat>, FpGroupError> {
let n = right_mult.len();
let rank = module.rank();
let rk_gens = module.group_rank();
let mut actions: Vec<Option<Mat>> = vec![None; n];
actions[0] = Some(identity_matrix(rank));
let mut queue = std::collections::VecDeque::new();
queue.push_back(0usize);
let mut visited = 1usize;
while let Some(c) = queue.pop_front() {
let current = actions[c].clone().ok_or_else(|| FpGroupError::Internal {
detail: format!("element {c} dequeued without an action matrix"),
})?;
for k in 0..rk_gens {
let target = right_mult[c][k];
let product = reduce(&mat_mul(¤t, &module.action[k]), &module.invariants);
for row in &product {
for e in row {
if e.significant_bits() > MAX_DERIVED_ENTRY_BITS {
return Err(FpGroupError::ActionNotWellDefined {
detail: "the matrix products grow without bound, so the generators \
cannot have finite order on M"
.into(),
});
}
}
}
match &actions[target] {
Some(existing) => {
if *existing != product {
return Err(FpGroupError::ActionNotWellDefined {
detail: format!(
"A(g*x) != A(g)*A(x) for element {c} and generator {k}, so some \
relator does not act as the identity on M"
),
});
}
}
None => {
actions[target] = Some(product);
visited += 1;
queue.push_back(target);
}
}
}
}
if visited != n {
return Err(FpGroupError::Internal {
detail: format!("Cayley graph walk reached {visited} of {n} elements"),
});
}
actions
.into_iter()
.map(|a| {
a.ok_or_else(|| FpGroupError::Internal {
detail: "element without an action matrix after the walk".into(),
})
})
.collect()
}
fn cochain_dimension(rank: usize, n: usize, degree: usize) -> Result<usize, FpGroupError> {
let tuples = n
.checked_pow(degree as u32)
.ok_or_else(|| FpGroupError::CohomologyTooLarge {
detail: format!("|G|^{degree} overflows for |G| = {n}"),
})?;
tuples
.checked_mul(rank)
.ok_or_else(|| FpGroupError::CohomologyTooLarge {
detail: format!("rank(M)*|G|^{degree} overflows for |G| = {n}"),
})
}
fn moduli(rank: usize, n: usize, degree: usize, invariants: &[Integer]) -> Vec<Integer> {
let tuples = n.pow(degree as u32);
let mut out = Vec::with_capacity(tuples * rank);
for _ in 0..tuples {
for i in 0..rank {
out.push(invariants[i].clone());
}
}
out
}
fn lattice_generators(moduli: &[Integer]) -> Vec<Vec<Integer>> {
let dim = moduli.len();
moduli
.iter()
.enumerate()
.filter(|(_, m)| **m > 0)
.map(|(p, m)| {
let mut row = vec![Integer::from(0); dim];
row[p] = m.clone();
row
})
.collect()
}
fn coboundary(
degree: usize,
n: usize,
rank: usize,
mult: &[Vec<usize>],
actions: &[Mat],
) -> Result<Vec<Vec<Integer>>, FpGroupError> {
let in_dim = cochain_dimension(rank, n, degree)?;
let out_dim = cochain_dimension(rank, n, degree + 1)?;
let mut m = vec![vec![Integer::from(0); out_dim]; in_dim];
match degree {
0 => {
for g in 0..n {
for i in 0..rank {
let out = g * rank + i;
for j in 0..rank {
let a = actions[g][i][j].clone();
m[j][out] += a;
}
m[i][out] -= 1;
}
}
}
1 => {
for x in 0..n {
for y in 0..n {
let out_tuple = x * n + y;
for i in 0..rank {
let out = out_tuple * rank + i;
for j in 0..rank {
let a = actions[x][i][j].clone();
m[y * rank + j][out] += a;
}
m[mult[x][y] * rank + i][out] -= 1;
m[x * rank + i][out] += 1;
}
}
}
}
2 => {
for x in 0..n {
for y in 0..n {
for z in 0..n {
let out_tuple = (x * n + y) * n + z;
for i in 0..rank {
let out = out_tuple * rank + i;
for j in 0..rank {
let a = actions[x][i][j].clone();
m[(y * n + z) * rank + j][out] += a;
}
m[(mult[x][y] * n + z) * rank + i][out] -= 1;
m[(x * n + mult[y][z]) * rank + i][out] += 1;
m[(x * n + y) * rank + i][out] -= 1;
}
}
}
}
}
d => {
return Err(FpGroupError::UnsupportedCohomologyDegree {
degree: d,
max: MAX_COHOMOLOGY_DEGREE,
})
}
}
Ok(m)
}
pub(super) fn cohomology(
group: &FpGroup,
degree: usize,
module: &GModule,
) -> Result<AbelianInvariants, FpGroupError> {
if degree > MAX_COHOMOLOGY_DEGREE {
return Err(FpGroupError::UnsupportedCohomologyDegree {
degree,
max: MAX_COHOMOLOGY_DEGREE,
});
}
if module.group_rank() != group.rank() {
return Err(FpGroupError::ModuleShape {
detail: format!(
"the module carries {} action matrices but the presentation has {} generators",
module.group_rank(),
group.rank()
),
});
}
let abelian = group.abelian_invariants()?;
if abelian.free_rank() > 0 {
return Err(FpGroupError::ProvablyInfinite {
abelian_invariants: abelian.to_string(),
});
}
let table = group.coset_table(&[])?;
let n = table.index();
if n > MAX_COHOMOLOGY_GROUP_ORDER {
return Err(FpGroupError::CohomologyTooLarge {
detail: format!(
"|G| = {n} is above MAX_COHOMOLOGY_GROUP_ORDER ({MAX_COHOMOLOGY_GROUP_ORDER})"
),
});
}
let rank = module.rank();
let top = cochain_dimension(rank, n, degree + 1)?;
if top > MAX_COCHAIN_DIMENSION {
return Err(FpGroupError::CohomologyTooLarge {
detail: format!(
"the degree-{} cochain group has rank(M)*|G|^{} = {top} generators, above \
MAX_COCHAIN_DIMENSION ({MAX_COCHAIN_DIMENSION})",
degree + 1,
degree + 1
),
});
}
let transversal = table.transversal()?;
let mut right_mult = vec![vec![0usize; group.rank()]; n];
for c in 0..n {
for k in 0..group.rank() {
right_mult[c][k] = table.image(c, k as i32 + 1)?;
}
}
let mut mult = vec![vec![0usize; n]; n];
for a in 0..n {
for b in 0..n {
mult[a][b] = table.trace(a, &transversal[b])?;
}
}
let actions = element_actions(&right_mult, module)?;
let dim = cochain_dimension(rank, n, degree)?;
let d_here = coboundary(degree, n, rank, &mult, &actions)?;
let moduli_here = moduli(rank, n, degree, module.invariants());
let moduli_above = moduli(rank, n, degree + 1, module.invariants());
let kernel = preimage_lattice_blocked(&d_here, dim, top, &moduli_above)?;
let mut image: Vec<Vec<Integer>> = if degree == 0 {
Vec::new()
} else {
coboundary(degree - 1, n, rank, &mult, &actions)?
};
image.extend(lattice_generators(&moduli_here));
quotient_invariants(dim, &kernel, &image)
}