rust_igraph/algorithms/isomorphism/canonical/
isomorphic_bliss.rs1use super::canonicalize;
4use crate::algorithms::isomorphism::vf2::Vf2Isomorphism;
5use crate::core::{Graph, IgraphError, IgraphResult};
6
7pub fn isomorphic_bliss(
51 graph1: &Graph,
52 graph2: &Graph,
53 vertex_color1: Option<&[u32]>,
54 vertex_color2: Option<&[u32]>,
55) -> IgraphResult<Vf2Isomorphism> {
56 if graph1.is_directed() != graph2.is_directed() {
57 return Err(IgraphError::InvalidArgument(
58 "cannot compare directed and undirected graphs".to_owned(),
59 ));
60 }
61
62 let (color1, color2) = if vertex_color1.is_some() == vertex_color2.is_some() {
64 (vertex_color1, vertex_color2)
65 } else {
66 (None, None)
67 };
68
69 let n1 = graph1.vcount() as usize;
70 let n2 = graph2.vcount() as usize;
71
72 let canon1 = canonicalize(graph1, color1)?;
75 let canon2 = canonicalize(graph2, color2)?;
76
77 let not_iso = || Vf2Isomorphism {
78 iso: false,
79 map12: Vec::new(),
80 map21: Vec::new(),
81 };
82
83 if n1 != n2 || canon1.certificate != canon2.certificate {
84 return Ok(not_iso());
85 }
86
87 let mut order1 = vec![0u32; n1];
90 for (v, &pos) in canon1.labeling.iter().enumerate() {
91 order1[pos as usize] = v as u32;
92 }
93 let mut order2 = vec![0u32; n2];
94 for (v, &pos) in canon2.labeling.iter().enumerate() {
95 order2[pos as usize] = v as u32;
96 }
97
98 if let (Some(c1), Some(c2)) = (color1, color2) {
101 for pos in 0..n1 {
102 if c1[order1[pos] as usize] != c2[order2[pos] as usize] {
103 return Ok(not_iso());
104 }
105 }
106 }
107
108 let mut map12 = vec![0u32; n1];
110 let mut map21 = vec![0u32; n2];
111 for (v1, &pos) in canon1.labeling.iter().enumerate() {
112 let v2 = order2[pos as usize];
113 map12[v1] = v2;
114 map21[v2 as usize] = v1 as u32;
115 }
116
117 Ok(Vf2Isomorphism {
118 iso: true,
119 map12,
120 map21,
121 })
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn triangle(perm: [u32; 3]) -> Graph {
129 let mut g = Graph::new(3, false).expect("graph");
130 g.add_edge(perm[0], perm[1]).expect("edge");
131 g.add_edge(perm[1], perm[2]).expect("edge");
132 g.add_edge(perm[2], perm[0]).expect("edge");
133 g
134 }
135
136 fn path(n: u32) -> Graph {
137 let mut g = Graph::new(n, false).expect("graph");
138 for i in 0..n.saturating_sub(1) {
139 g.add_edge(i, i + 1).expect("edge");
140 }
141 g
142 }
143
144 fn complete(n: u32) -> Graph {
145 let mut g = Graph::new(n, false).expect("graph");
146 for a in 0..n {
147 for b in (a + 1)..n {
148 g.add_edge(a, b).expect("edge");
149 }
150 }
151 g
152 }
153
154 fn directed_cycle(n: u32) -> Graph {
155 let mut g = Graph::new(n, true).expect("graph");
156 for i in 0..n {
157 g.add_edge(i, (i + 1) % n).expect("edge");
158 }
159 g
160 }
161
162 #[allow(clippy::many_single_char_names)]
164 fn assert_valid_map(a: &Graph, b: &Graph, r: &Vf2Isomorphism) {
165 let n = a.vcount() as usize;
166 assert_eq!(r.map12.len(), n);
167 assert_eq!(r.map21.len(), b.vcount() as usize);
168 let mut seen = vec![false; n];
170 for (v, &iv) in r.map12.iter().enumerate() {
171 assert!(!seen[iv as usize], "map12 not injective");
172 seen[iv as usize] = true;
173 assert_eq!(r.map21[iv as usize], v as u32, "map21 not inverse of map12");
174 }
175 let b_edges: std::collections::HashSet<(u32, u32)> = (0..b.ecount())
177 .map(|e| b.edge(e as u32).expect("edge"))
178 .map(|(u, v)| {
179 if b.is_directed() || u <= v {
180 (u, v)
181 } else {
182 (v, u)
183 }
184 })
185 .collect();
186 for e in 0..a.ecount() {
187 let (u, v) = a.edge(e as u32).expect("edge");
188 let (iu, iv) = (r.map12[u as usize], r.map12[v as usize]);
189 let key = if a.is_directed() || iu <= iv {
190 (iu, iv)
191 } else {
192 (iv, iu)
193 };
194 assert!(b_edges.contains(&key), "edge not preserved by map");
195 }
196 }
197
198 #[test]
199 fn relabelled_triangles_are_isomorphic() {
200 let a = triangle([0, 1, 2]);
201 let b = triangle([2, 0, 1]);
202 let r = isomorphic_bliss(&a, &b, None, None).expect("bliss");
203 assert!(r.iso);
204 assert_valid_map(&a, &b, &r);
205 }
206
207 #[test]
208 fn triangle_is_not_a_path() {
209 let r = isomorphic_bliss(&triangle([0, 1, 2]), &path(3), None, None).expect("bliss");
210 assert!(!r.iso);
211 assert!(r.map12.is_empty());
212 assert!(r.map21.is_empty());
213 }
214
215 #[test]
216 fn different_vertex_counts_are_not_isomorphic() {
217 let r = isomorphic_bliss(&complete(4), &complete(5), None, None).expect("bliss");
218 assert!(!r.iso);
219 }
220
221 #[test]
222 fn complete_graphs_match_under_any_relabelling() {
223 let a = complete(5);
224 let b = complete(5);
225 let r = isomorphic_bliss(&a, &b, None, None).expect("bliss");
226 assert!(r.iso);
227 assert_valid_map(&a, &b, &r);
228 }
229
230 #[test]
231 fn directed_cycles_respect_orientation() {
232 let a = directed_cycle(4);
233 let b = directed_cycle(4);
234 let r = isomorphic_bliss(&a, &b, None, None).expect("bliss");
235 assert!(r.iso);
236 assert_valid_map(&a, &b, &r);
237 }
238
239 #[test]
240 fn directedness_mismatch_is_an_error() {
241 let und = path(3);
242 let dir = directed_cycle(3);
243 assert!(isomorphic_bliss(&und, &dir, None, None).is_err());
244 }
245
246 #[test]
247 fn colours_must_match_for_isomorphism() {
248 let a = path(3);
251 let b = path(3);
252 let ca = [0u32, 1, 0];
254 let cb = [1u32, 0, 1];
256 let r = isomorphic_bliss(&a, &b, Some(&ca), Some(&cb)).expect("bliss");
257 assert!(!r.iso);
258 }
259
260 #[test]
261 fn colour_matching_uses_raw_values_not_class_structure() {
262 let a = path(3);
267 let b = path(3);
268 let ca = [5u32, 6, 5];
269 let cb = [7u32, 8, 7];
270 let r = isomorphic_bliss(&a, &b, Some(&ca), Some(&cb)).expect("bliss");
271 assert!(!r.iso);
272 }
273
274 #[test]
275 fn matching_colours_yield_isomorphism() {
276 let a = path(3);
277 let b = path(3);
278 let ca = [0u32, 1, 0];
279 let cb = [0u32, 1, 0];
280 let r = isomorphic_bliss(&a, &b, Some(&ca), Some(&cb)).expect("bliss");
281 assert!(r.iso);
282 assert_valid_map(&a, &b, &r);
283 }
284
285 #[test]
286 fn one_sided_colour_is_ignored() {
287 let a = triangle([0, 1, 2]);
289 let b = triangle([1, 2, 0]);
290 let ca = [0u32, 1, 2];
291 let r = isomorphic_bliss(&a, &b, Some(&ca), None).expect("bliss");
292 assert!(r.iso);
293 }
294
295 #[test]
296 fn wrong_colour_length_is_an_error() {
297 let a = triangle([0, 1, 2]);
298 let b = triangle([0, 1, 2]);
299 let bad = [0u32, 0];
300 assert!(isomorphic_bliss(&a, &b, Some(&bad), Some(&[0, 0, 0])).is_err());
301 }
302
303 #[test]
304 fn rejects_multigraph() {
305 let mut a = Graph::new(2, false).expect("graph");
306 a.add_edge(0, 1).expect("edge");
307 a.add_edge(0, 1).expect("edge");
308 let b = path(2);
309 assert!(isomorphic_bliss(&a, &b, None, None).is_err());
310 }
311}