rust_igraph/algorithms/isomorphism/canonical/canonical_permutation.rs
1//! `canonical_permutation` (`ALGO-ISO-003`).
2
3use super::canonicalize;
4use crate::core::{Graph, IgraphResult};
5
6/// Compute a canonical vertex permutation of `graph`.
7///
8/// The returned vector `labeling` has one entry per vertex: `labeling[v]` is
9/// the canonical position assigned to vertex `v` (a vertex → position map).
10/// Two graphs are isomorphic if and only if relabeling each by its canonical
11/// permutation produces the same graph, so canonical permutations give an
12/// isomorphism test by comparison of canonical forms.
13///
14/// `colors` is an optional per-vertex colour slice (length must equal the
15/// vertex count); vertices of different colours are never identified. Pass
16/// `None` for an uncoloured graph.
17///
18/// This mirrors igraph's `igraph_canonical_permutation` (BLISS-backed
19/// upstream). The *specific* labeling is implementation-defined — only the
20/// resulting canonical form is meaningful — so the labeling here need not
21/// match igraph's byte-for-byte, but the induced canonical form is a valid
22/// isomorphism invariant.
23///
24/// # Scope
25///
26/// Simple graphs (directed or undirected), self-loops allowed, optional
27/// vertex colours. Multi-edges are rejected.
28///
29/// # Errors
30///
31/// Returns an error if `colors` has the wrong length or the graph has
32/// multiple edges between the same pair of vertices.
33///
34/// # Examples
35///
36/// ```
37/// use rust_igraph::{Graph, canonical_permutation, permute_vertices};
38///
39/// // Two differently-labeled copies of the path 0-1-2.
40/// let mut a = Graph::new(3, false).unwrap();
41/// a.add_edge(0, 1).unwrap();
42/// a.add_edge(1, 2).unwrap();
43///
44/// let mut b = Graph::new(3, false).unwrap();
45/// b.add_edge(2, 1).unwrap();
46/// b.add_edge(1, 0).unwrap();
47///
48/// let pa = canonical_permutation(&a, None).unwrap();
49/// let pb = canonical_permutation(&b, None).unwrap();
50/// assert_eq!(pa.len(), 3);
51///
52/// // Relabeling each graph to canonical form yields identical edge sets.
53/// // `permute_vertices` wants a position -> vertex map, the inverse of `labeling`.
54/// let inv = |p: &[u32]| -> Vec<u32> {
55/// let mut q = vec![0u32; p.len()];
56/// for (v, &pos) in p.iter().enumerate() {
57/// q[pos as usize] = v as u32;
58/// }
59/// q
60/// };
61/// let ca = permute_vertices(&a, &inv(&pa)).unwrap();
62/// let cb = permute_vertices(&b, &inv(&pb)).unwrap();
63/// let mut ea: Vec<_> = (0..ca.ecount()).map(|e| ca.edge(e as u32).unwrap()).collect();
64/// let mut eb: Vec<_> = (0..cb.ecount()).map(|e| cb.edge(e as u32).unwrap()).collect();
65/// ea.sort_unstable();
66/// eb.sort_unstable();
67/// assert_eq!(ea, eb);
68/// ```
69pub fn canonical_permutation(graph: &Graph, colors: Option<&[u32]>) -> IgraphResult<Vec<u32>> {
70 Ok(canonicalize(graph, colors)?.labeling)
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::algorithms::operators::permute_vertices::permute_vertices;
77
78 /// Apply a vertex → position labeling to produce the canonical graph and
79 /// return its sorted, direction-aware edge list.
80 fn canon_edges(g: &Graph, labeling: &[u32]) -> Vec<(u32, u32)> {
81 let mut edges: Vec<(u32, u32)> = (0..g.ecount())
82 .map(|e| {
83 let (u, v) = g.edge(e as u32).expect("edge in range");
84 let (cu, cv) = (labeling[u as usize], labeling[v as usize]);
85 if g.is_directed() || cu <= cv {
86 (cu, cv)
87 } else {
88 (cv, cu)
89 }
90 })
91 .collect();
92 edges.sort_unstable();
93 edges
94 }
95
96 fn cycle(n: u32, directed: bool) -> Graph {
97 let mut g = Graph::new(n, directed).expect("graph");
98 for i in 0..n {
99 g.add_edge(i, (i + 1) % n).expect("edge");
100 }
101 g
102 }
103
104 #[test]
105 fn labeling_is_a_permutation() {
106 let g = cycle(6, false);
107 let p = canonical_permutation(&g, None).expect("canon");
108 let mut sorted = p.clone();
109 sorted.sort_unstable();
110 assert_eq!(sorted, (0..6).collect::<Vec<_>>());
111 }
112
113 #[test]
114 fn empty_and_singleton() {
115 let e = Graph::new(0, false).expect("graph");
116 assert!(canonical_permutation(&e, None).expect("canon").is_empty());
117
118 let s = Graph::new(1, false).expect("graph");
119 assert_eq!(canonical_permutation(&s, None).expect("canon"), vec![0]);
120 }
121
122 #[test]
123 fn invariant_under_relabeling_undirected() {
124 let g = cycle(7, false);
125 // Reverse relabeling: position i holds old vertex (6 - i).
126 let perm: Vec<u32> = (0..7).rev().collect();
127 let h = permute_vertices(&g, &perm).expect("permute");
128
129 let pg = canonical_permutation(&g, None).expect("canon g");
130 let ph = canonical_permutation(&h, None).expect("canon h");
131 assert_eq!(canon_edges(&g, &pg), canon_edges(&h, &ph));
132 }
133
134 #[test]
135 fn invariant_under_relabeling_directed() {
136 let mut g = Graph::new(4, true).expect("graph");
137 for (u, v) in [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)] {
138 g.add_edge(u, v).expect("edge");
139 }
140 let perm = [2u32, 0, 3, 1];
141 let h = permute_vertices(&g, &perm).expect("permute");
142
143 let pg = canonical_permutation(&g, None).expect("canon g");
144 let ph = canonical_permutation(&h, None).expect("canon h");
145 assert_eq!(canon_edges(&g, &pg), canon_edges(&h, &ph));
146 }
147
148 #[test]
149 fn non_isomorphic_graphs_differ() {
150 // Path 0-1-2-3 vs star centered at 0.
151 let mut path = Graph::new(4, false).expect("graph");
152 for (u, v) in [(0, 1), (1, 2), (2, 3)] {
153 path.add_edge(u, v).expect("edge");
154 }
155 let mut star = Graph::new(4, false).expect("graph");
156 for v in 1..4 {
157 star.add_edge(0, v).expect("edge");
158 }
159 let pp = canonical_permutation(&path, None).expect("canon");
160 let ps = canonical_permutation(&star, None).expect("canon");
161 assert_ne!(canon_edges(&path, &pp), canon_edges(&star, &ps));
162 }
163
164 #[test]
165 fn respects_vertex_colors() {
166 // Two-vertex single edge; distinct colours forbid swapping them, so
167 // the colour-0 vertex must always map to canonical position 0.
168 let mut g = Graph::new(2, false).expect("graph");
169 g.add_edge(0, 1).expect("edge");
170 let colors = [0u32, 1];
171 let p = canonical_permutation(&g, Some(&colors)).expect("canon");
172 assert_eq!(p[0], 0);
173 assert_eq!(p[1], 1);
174 }
175
176 #[test]
177 fn rejects_multigraph() {
178 let mut g = Graph::new(2, false).expect("graph");
179 g.add_edge(0, 1).expect("edge");
180 g.add_edge(0, 1).expect("edge");
181 assert!(canonical_permutation(&g, None).is_err());
182 }
183
184 #[test]
185 fn rejects_wrong_color_length() {
186 let g = cycle(3, false);
187 let colors = [0u32, 1];
188 assert!(canonical_permutation(&g, Some(&colors)).is_err());
189 }
190}