use super::canonicalize;
use crate::core::{Graph, IgraphResult};
pub fn automorphism_group(graph: &Graph, colors: Option<&[u32]>) -> IgraphResult<Vec<Vec<u32>>> {
Ok(canonicalize(graph, colors)?.generators)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn cycle(n: u32, directed: bool) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
for i in 0..n {
g.add_edge(i, (i + 1) % n).expect("edge");
}
g
}
fn complete(n: u32) -> Graph {
let mut g = Graph::new(n, false).expect("graph");
for u in 0..n {
for v in (u + 1)..n {
g.add_edge(u, v).expect("edge");
}
}
g
}
fn petersen() -> Graph {
let mut g = Graph::new(10, false).expect("graph");
for i in 0..5 {
g.add_edge(i, (i + 1) % 5).expect("edge");
g.add_edge(i + 5, (i + 2) % 5 + 5).expect("edge");
g.add_edge(i, i + 5).expect("edge");
}
g
}
#[allow(clippy::many_single_char_names)]
fn matrix(g: &Graph) -> Vec<Vec<bool>> {
let n = g.vcount() as usize;
let mut m = vec![vec![false; n]; n];
for eid in 0..g.ecount() {
let (u, v) = g.edge(u32::try_from(eid).expect("eid")).expect("edge");
m[u as usize][v as usize] = true;
if !g.is_directed() {
m[v as usize][u as usize] = true;
}
}
m
}
fn is_automorphism(g: &Graph, perm: &[u32]) -> bool {
let n = g.vcount() as usize;
if perm.len() != n {
return false;
}
let mut seen = vec![false; n];
for &p in perm {
if seen[p as usize] {
return false;
}
seen[p as usize] = true;
}
let m = matrix(g);
for u in 0..n {
for v in 0..n {
if m[u][v] != m[perm[u] as usize][perm[v] as usize] {
return false;
}
}
}
true
}
fn compose(a: &[u32], b: &[u32]) -> Vec<u32> {
b.iter().map(|&bv| a[bv as usize]).collect()
}
fn closure_order(gens: &[Vec<u32>], n: usize) -> usize {
let id: Vec<u32> = (0..n as u32).collect();
let mut set: HashSet<Vec<u32>> = HashSet::new();
set.insert(id.clone());
let mut frontier = vec![id];
while let Some(p) = frontier.pop() {
for g in gens {
let q = compose(g, &p);
if set.insert(q.clone()) {
frontier.push(q);
}
}
}
set.len()
}
#[test]
fn every_generator_is_an_automorphism() {
for g in [complete(5), cycle(6, false), cycle(5, true), petersen()] {
let gens = automorphism_group(&g, None).expect("gens");
for aut in &gens {
assert!(is_automorphism(&g, aut), "generator is not an automorphism");
}
}
}
#[test]
fn generators_generate_full_group() {
let n = 5u32;
assert_eq!(
closure_order(
&automorphism_group(&complete(n), None).expect("g"),
n as usize
),
120 );
assert_eq!(
closure_order(&automorphism_group(&cycle(6, false), None).expect("g"), 6),
12 );
assert_eq!(
closure_order(&automorphism_group(&cycle(5, true), None).expect("g"), 5),
5 );
assert_eq!(
closure_order(&automorphism_group(&petersen(), None).expect("g"), 10),
120
);
}
#[test]
fn trivial_group_has_no_generators() {
let g = Graph::new(1, false).expect("graph");
assert!(automorphism_group(&g, None).expect("g").is_empty());
}
#[test]
fn colors_restrict_the_group() {
let g = complete(4);
let colors = [0u32, 0, 1, 1];
let gens = automorphism_group(&g, Some(&colors)).expect("g");
for aut in &gens {
assert!(is_automorphism(&g, aut));
for (v, &img) in aut.iter().enumerate() {
assert_eq!(colors[v], colors[img as usize], "colour not preserved");
}
}
assert_eq!(closure_order(&gens, 4), 4);
}
#[test]
fn rejects_multigraph() {
let mut g = Graph::new(2, false).expect("graph");
g.add_edge(0, 1).expect("edge");
g.add_edge(0, 1).expect("edge");
assert!(automorphism_group(&g, None).is_err());
}
#[test]
fn rejects_wrong_color_length() {
let g = cycle(3, false);
let colors = [0u32, 1];
assert!(automorphism_group(&g, Some(&colors)).is_err());
}
}