Skip to main content

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}