rust_igraph/algorithms/isomorphism/canonical/
automorphism_group.rs1use super::canonicalize;
4use crate::core::{Graph, IgraphResult};
5
6pub fn automorphism_group(graph: &Graph, colors: Option<&[u32]>) -> IgraphResult<Vec<Vec<u32>>> {
56 Ok(canonicalize(graph, colors)?.generators)
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use std::collections::HashSet;
63
64 fn cycle(n: u32, directed: bool) -> Graph {
65 let mut g = Graph::new(n, directed).expect("graph");
66 for i in 0..n {
67 g.add_edge(i, (i + 1) % n).expect("edge");
68 }
69 g
70 }
71
72 fn complete(n: u32) -> Graph {
73 let mut g = Graph::new(n, false).expect("graph");
74 for u in 0..n {
75 for v in (u + 1)..n {
76 g.add_edge(u, v).expect("edge");
77 }
78 }
79 g
80 }
81
82 fn petersen() -> Graph {
83 let mut g = Graph::new(10, false).expect("graph");
84 for i in 0..5 {
85 g.add_edge(i, (i + 1) % 5).expect("edge");
86 g.add_edge(i + 5, (i + 2) % 5 + 5).expect("edge");
87 g.add_edge(i, i + 5).expect("edge");
88 }
89 g
90 }
91
92 #[allow(clippy::many_single_char_names)]
95 fn matrix(g: &Graph) -> Vec<Vec<bool>> {
96 let n = g.vcount() as usize;
97 let mut m = vec![vec![false; n]; n];
98 for eid in 0..g.ecount() {
99 let (u, v) = g.edge(u32::try_from(eid).expect("eid")).expect("edge");
100 m[u as usize][v as usize] = true;
101 if !g.is_directed() {
102 m[v as usize][u as usize] = true;
103 }
104 }
105 m
106 }
107
108 fn is_automorphism(g: &Graph, perm: &[u32]) -> bool {
109 let n = g.vcount() as usize;
110 if perm.len() != n {
111 return false;
112 }
113 let mut seen = vec![false; n];
115 for &p in perm {
116 if seen[p as usize] {
117 return false;
118 }
119 seen[p as usize] = true;
120 }
121 let m = matrix(g);
122 for u in 0..n {
123 for v in 0..n {
124 if m[u][v] != m[perm[u] as usize][perm[v] as usize] {
125 return false;
126 }
127 }
128 }
129 true
130 }
131
132 fn compose(a: &[u32], b: &[u32]) -> Vec<u32> {
133 b.iter().map(|&bv| a[bv as usize]).collect()
134 }
135
136 fn closure_order(gens: &[Vec<u32>], n: usize) -> usize {
138 let id: Vec<u32> = (0..n as u32).collect();
139 let mut set: HashSet<Vec<u32>> = HashSet::new();
140 set.insert(id.clone());
141 let mut frontier = vec![id];
142 while let Some(p) = frontier.pop() {
143 for g in gens {
144 let q = compose(g, &p);
145 if set.insert(q.clone()) {
146 frontier.push(q);
147 }
148 }
149 }
150 set.len()
151 }
152
153 #[test]
154 fn every_generator_is_an_automorphism() {
155 for g in [complete(5), cycle(6, false), cycle(5, true), petersen()] {
156 let gens = automorphism_group(&g, None).expect("gens");
157 for aut in &gens {
158 assert!(is_automorphism(&g, aut), "generator is not an automorphism");
159 }
160 }
161 }
162
163 #[test]
164 fn generators_generate_full_group() {
165 let n = 5u32;
167 assert_eq!(
168 closure_order(
169 &automorphism_group(&complete(n), None).expect("g"),
170 n as usize
171 ),
172 120 );
174 assert_eq!(
175 closure_order(&automorphism_group(&cycle(6, false), None).expect("g"), 6),
176 12 );
178 assert_eq!(
179 closure_order(&automorphism_group(&cycle(5, true), None).expect("g"), 5),
180 5 );
182 assert_eq!(
183 closure_order(&automorphism_group(&petersen(), None).expect("g"), 10),
184 120
185 );
186 }
187
188 #[test]
189 fn trivial_group_has_no_generators() {
190 let g = Graph::new(1, false).expect("graph");
194 assert!(automorphism_group(&g, None).expect("g").is_empty());
195 }
196
197 #[test]
198 fn colors_restrict_the_group() {
199 let g = complete(4);
201 let colors = [0u32, 0, 1, 1];
202 let gens = automorphism_group(&g, Some(&colors)).expect("g");
203 for aut in &gens {
204 assert!(is_automorphism(&g, aut));
206 for (v, &img) in aut.iter().enumerate() {
207 assert_eq!(colors[v], colors[img as usize], "colour not preserved");
208 }
209 }
210 assert_eq!(closure_order(&gens, 4), 4);
211 }
212
213 #[test]
214 fn rejects_multigraph() {
215 let mut g = Graph::new(2, false).expect("graph");
216 g.add_edge(0, 1).expect("edge");
217 g.add_edge(0, 1).expect("edge");
218 assert!(automorphism_group(&g, None).is_err());
219 }
220
221 #[test]
222 fn rejects_wrong_color_length() {
223 let g = cycle(3, false);
224 let colors = [0u32, 1];
225 assert!(automorphism_group(&g, Some(&colors)).is_err());
226 }
227}