use super::{GroupError, Permutation};
use rug::Integer;
use std::sync::OnceLock;
pub const MAX_BSGS_DEGREE: usize = 256;
pub const DEFAULT_ELEMENT_CAP: u64 = 100_000;
pub const MAX_ELEMENT_CAP: u64 = 1_000_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SchreierEntry {
pub generator: usize,
pub from: usize,
}
#[derive(Clone, Debug)]
pub struct Orbit {
base_point: usize,
degree: usize,
points: Vec<usize>,
schreier: Vec<Option<SchreierEntry>>,
transversal: Vec<Option<Permutation>>,
}
impl Orbit {
pub fn base_point(&self) -> usize {
self.base_point
}
pub fn degree(&self) -> usize {
self.degree
}
pub fn points(&self) -> &[usize] {
&self.points
}
pub fn sorted_points(&self) -> Vec<usize> {
let mut p = self.points.clone();
p.sort_unstable();
p
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
false
}
pub fn contains(&self, point: usize) -> bool {
point < self.degree && self.transversal[point].is_some()
}
pub fn schreier_vector(&self) -> &[Option<SchreierEntry>] {
&self.schreier
}
pub fn transversal_element(&self, point: usize) -> Option<&Permutation> {
self.transversal.get(point).and_then(|u| u.as_ref())
}
pub fn transversal_from_schreier_vector(
&self,
point: usize,
generators: &[Permutation],
) -> Option<Permutation> {
if !self.contains(point) {
return None;
}
let mut path = Vec::new();
let mut current = point;
while let Some(entry) = self.schreier[current] {
path.push(entry.generator);
current = entry.from;
}
debug_assert_eq!(current, self.base_point);
let mut u = Permutation::identity(self.degree);
for &g in path.iter().rev() {
u = u.compose_unchecked(generators.get(g)?);
}
Some(u)
}
}
fn build_orbit(degree: usize, generators: &[Permutation], base: usize) -> Orbit {
let mut schreier: Vec<Option<SchreierEntry>> = vec![None; degree];
let mut transversal: Vec<Option<Permutation>> = vec![None; degree];
let mut points = Vec::new();
if base < degree {
points.push(base);
transversal[base] = Some(Permutation::identity(degree));
}
let mut head = 0;
while head < points.len() {
let alpha = points[head];
head += 1;
let u_alpha = transversal[alpha]
.clone()
.expect("a discovered point has a transversal element");
for (index, g) in generators.iter().enumerate() {
let beta = g.img(alpha);
if transversal[beta].is_none() {
transversal[beta] = Some(u_alpha.compose_unchecked(g));
schreier[beta] = Some(SchreierEntry {
generator: index,
from: alpha,
});
points.push(beta);
}
}
}
Orbit {
base_point: base,
degree,
points,
schreier,
transversal,
}
}
#[derive(Clone, Debug)]
pub struct StabilizerLevel {
base_point: usize,
generators: Vec<Permutation>,
orbit: Orbit,
}
impl StabilizerLevel {
pub fn base_point(&self) -> usize {
self.base_point
}
pub fn generators(&self) -> &[Permutation] {
&self.generators
}
pub fn orbit(&self) -> &Orbit {
&self.orbit
}
}
#[derive(Clone, Debug)]
pub struct StabilizerChain {
degree: usize,
levels: Vec<StabilizerLevel>,
}
impl StabilizerChain {
pub fn degree(&self) -> usize {
self.degree
}
pub fn base(&self) -> Vec<usize> {
self.levels.iter().map(|l| l.base_point).collect()
}
pub fn levels(&self) -> &[StabilizerLevel] {
&self.levels
}
pub fn strong_generators(&self) -> Vec<Permutation> {
let mut all: Vec<Permutation> = self
.levels
.iter()
.flat_map(|l| l.generators.iter().cloned())
.collect();
all.sort();
all.dedup();
all
}
pub fn order(&self) -> Integer {
let mut order = Integer::from(1);
for level in &self.levels {
order *= level.orbit.len() as u64;
}
order
}
pub fn sift(&self, g: &Permutation) -> Result<SiftResult, GroupError> {
if g.degree() != self.degree {
return Err(GroupError::DegreeMismatch {
left: self.degree,
right: g.degree(),
});
}
let (residue, level) = self.strip(g, 0);
let is_member = residue.is_identity();
Ok(SiftResult {
residue,
level,
is_member,
})
}
fn strip(&self, g: &Permutation, from_level: usize) -> (Permutation, usize) {
let mut h = g.clone();
for (index, level) in self.levels.iter().enumerate().skip(from_level) {
let image = h.img(level.base_point);
match level.orbit.transversal_element(image) {
None => return (h, index),
Some(u) => h = h.compose_unchecked(&u.inverse()),
}
}
(h, self.levels.len())
}
}
#[derive(Clone, Debug)]
pub struct SiftResult {
residue: Permutation,
level: usize,
is_member: bool,
}
impl SiftResult {
pub fn residue(&self) -> &Permutation {
&self.residue
}
pub fn level(&self) -> usize {
self.level
}
pub fn is_member(&self) -> bool {
self.is_member
}
}
struct ChainBuilder {
degree: usize,
base: Vec<usize>,
generators: Vec<Vec<Permutation>>,
orbits: Vec<Orbit>,
}
impl ChainBuilder {
fn strip(&self, g: &Permutation, from_level: usize) -> (Permutation, usize) {
let mut h = g.clone();
for index in from_level..self.base.len() {
let image = h.img(self.base[index]);
match self.orbits[index].transversal_element(image) {
None => return (h, index),
Some(u) => h = h.compose_unchecked(&u.inverse()),
}
}
(h, self.base.len())
}
fn complete_level(&mut self, i: usize) {
self.orbits[i] = build_orbit(self.degree, &self.generators[i], self.base[i]);
let points = self.orbits[i].points().to_vec();
let generator_count = self.generators[i].len();
for beta in points {
let u_beta = self.orbits[i]
.transversal_element(beta)
.expect("orbit point has a transversal element")
.clone();
for xi in 0..generator_count {
let x = self.generators[i][xi].clone();
let image = x.img(beta);
let u_image = self.orbits[i]
.transversal_element(image)
.expect("the image of an orbit point is in the orbit")
.clone();
let schreier_generator = u_beta
.compose_unchecked(&x)
.compose_unchecked(&u_image.inverse());
debug_assert_eq!(schreier_generator.img(self.base[i]), self.base[i]);
if schreier_generator.is_identity() {
continue;
}
let (residue, mut stop) = self.strip(&schreier_generator, i + 1);
if residue.is_identity() {
continue;
}
if stop == self.base.len() {
let new_point = residue
.first_moved_point()
.expect("a non-identity permutation moves a point");
self.base.push(new_point);
self.generators.push(Vec::new());
self.orbits.push(build_orbit(self.degree, &[], new_point));
stop = self.base.len() - 1;
}
for level in (i + 1)..=stop {
self.generators[level].push(residue.clone());
}
for level in ((i + 1)..=stop).rev() {
self.complete_level(level);
}
}
}
}
}
pub(super) fn schreier_sims(
degree: usize,
generators: &[Permutation],
) -> Result<StabilizerChain, GroupError> {
if degree > MAX_BSGS_DEGREE {
return Err(GroupError::DegreeTooLargeForBsgs {
degree,
max: MAX_BSGS_DEGREE,
});
}
for g in generators {
if g.degree() != degree {
return Err(GroupError::DegreeMismatch {
left: degree,
right: g.degree(),
});
}
}
let mut base: Vec<usize> = Vec::new();
for g in generators {
if g.is_identity() {
continue;
}
if base.iter().all(|&b| g.img(b) == b) {
base.push(
g.first_moved_point()
.expect("a non-identity permutation moves a point"),
);
}
}
let mut level_generators: Vec<Vec<Permutation>> = vec![Vec::new(); base.len()];
for g in generators {
if g.is_identity() {
continue;
}
for (level, &b) in base.iter().enumerate() {
level_generators[level].push(g.clone());
if g.img(b) != b {
break;
}
}
}
let orbits: Vec<Orbit> = base
.iter()
.enumerate()
.map(|(i, &b)| build_orbit(degree, &level_generators[i], b))
.collect();
let mut builder = ChainBuilder {
degree,
base,
generators: level_generators,
orbits,
};
let mut i = builder.base.len();
while i > 0 {
i -= 1;
builder.complete_level(i);
}
let levels = builder
.base
.iter()
.enumerate()
.map(|(index, &base_point)| StabilizerLevel {
base_point,
generators: builder.generators[index].clone(),
orbit: builder.orbits[index].clone(),
})
.collect();
Ok(StabilizerChain { degree, levels })
}
#[derive(Clone, Debug)]
pub struct PermutationGroup {
degree: usize,
generators: Vec<Permutation>,
chain: OnceLock<Result<StabilizerChain, GroupError>>,
}
impl PermutationGroup {
pub fn new(degree: usize, generators: Vec<Permutation>) -> Result<Self, GroupError> {
for g in &generators {
if g.degree() != degree {
return Err(GroupError::DegreeMismatch {
left: degree,
right: g.degree(),
});
}
}
Ok(PermutationGroup {
degree,
generators,
chain: OnceLock::new(),
})
}
pub fn from_generators(generators: Vec<Permutation>) -> Result<Self, GroupError> {
let degree =
generators
.first()
.map(|g| g.degree())
.ok_or(GroupError::UnsupportedDegree {
family: "from_generators",
requested: 0,
reason: "an empty generator list does not determine a degree; use \
PermutationGroup::new(degree, vec![]) for the trivial group",
})?;
PermutationGroup::new(degree, generators)
}
pub fn degree(&self) -> usize {
self.degree
}
pub fn generators(&self) -> &[Permutation] {
&self.generators
}
pub fn is_trivial(&self) -> bool {
self.generators.iter().all(|g| g.is_identity())
}
pub fn orbit(&self, point: usize) -> Result<Orbit, GroupError> {
if point >= self.degree {
return Err(GroupError::PointOutOfRange {
point,
degree: self.degree,
});
}
Ok(build_orbit(self.degree, &self.generators, point))
}
pub fn orbits(&self) -> Vec<Vec<usize>> {
let mut seen = vec![false; self.degree];
let mut out = Vec::new();
for point in 0..self.degree {
if seen[point] {
continue;
}
let points = build_orbit(self.degree, &self.generators, point).sorted_points();
for &p in &points {
seen[p] = true;
}
out.push(points);
}
out
}
pub fn is_transitive(&self) -> bool {
self.degree > 0 && build_orbit(self.degree, &self.generators, 0).len() == self.degree
}
pub fn stabilizer_chain(&self) -> Result<&StabilizerChain, GroupError> {
self.chain
.get_or_init(|| schreier_sims(self.degree, &self.generators))
.as_ref()
.map_err(|e| e.clone())
}
pub fn order(&self) -> Result<Integer, GroupError> {
Ok(self.stabilizer_chain()?.order())
}
pub fn contains(&self, g: &Permutation) -> Result<bool, GroupError> {
Ok(self.sift(g)?.is_member())
}
pub fn sift(&self, g: &Permutation) -> Result<SiftResult, GroupError> {
if g.degree() != self.degree {
return Err(GroupError::DegreeMismatch {
left: self.degree,
right: g.degree(),
});
}
self.stabilizer_chain()?.sift(g)
}
pub fn random_element(&self, seed: u64) -> Result<Permutation, GroupError> {
let chain = self.stabilizer_chain()?;
let mut rng = SplitMix64::new(seed);
let mut element = Permutation::identity(self.degree);
for level in chain.levels().iter().rev() {
let points = level.orbit().points();
let beta = points[rng.below(points.len())];
let u = level
.orbit()
.transversal_element(beta)
.expect("orbit point has a transversal element");
element = element.compose_unchecked(u);
}
Ok(element)
}
pub fn elements(&self) -> Result<Vec<Permutation>, GroupError> {
self.elements_with_cap(DEFAULT_ELEMENT_CAP)
}
pub fn elements_with_cap(&self, cap: u64) -> Result<Vec<Permutation>, GroupError> {
let cap = cap.min(MAX_ELEMENT_CAP);
let chain = self.stabilizer_chain()?;
let order = chain.order();
if order > cap {
return Err(GroupError::EnumerationTooLarge {
order: order.to_string(),
cap,
});
}
let mut elements = vec![Permutation::identity(self.degree)];
for level in chain.levels().iter().rev() {
let mut next = Vec::with_capacity(elements.len() * level.orbit().len());
for h in &elements {
for &beta in level.orbit().points() {
let u = level
.orbit()
.transversal_element(beta)
.expect("orbit point has a transversal element");
next.push(h.compose_unchecked(u));
}
}
elements = next;
}
Ok(elements)
}
}
struct SplitMix64(u64);
impl SplitMix64 {
fn new(seed: u64) -> SplitMix64 {
SplitMix64(seed)
}
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, n: usize) -> usize {
debug_assert!(n > 0);
(self.next_u64() % n as u64) as usize
}
}