Skip to main content

rust_igraph/algorithms/operators/
is_same_graph.rs

1//! Structural graph equality (ALGO-CORE-001e, `is_same_graph` slice).
2//!
3//! Counterpart of `igraph_is_same_graph()` from
4//! `references/igraph/src/graph/type_indexededgelist.c:1947`.
5//! Returns `true` iff two graphs have the same vertex count, the
6//! same directedness, and the same edge multiset. Edge ordering
7//! and the orientation of undirected edges are normalised away —
8//! only set-equality of edges matters.
9//!
10//! This is **not** isomorphism: `0-1, 1-2` and `0-2, 1-2` are
11//! isomorphic but not "the same graph" because they use different
12//! edges. The vertex labels (0..vcount) are taken as ground truth.
13
14use crate::core::Graph;
15
16/// Returns `true` iff `g1` and `g2` are identical as labelled
17/// graphs: same vertex count, same directedness, same edge
18/// multiset. Edges differing only in storage order (or, for
19/// undirected, in endpoint orientation) are treated as identical.
20///
21/// Time complexity: `O(E log E)` due to the canonical sort of the
22/// two edge lists — slightly worse than upstream's `O(E)` walk over
23/// pre-sorted `ii` indices, but our `Graph` keeps that sort as a
24/// private detail so we re-sort here. Edge counts up to a few
25/// million sort in well under a second.
26///
27/// Note this is **not** isomorphism: vertex labels matter. Use a
28/// dedicated isomorphism checker (future AWU) if you need to ignore
29/// relabelling.
30///
31/// Counterpart of `igraph_is_same_graph` from
32/// `references/igraph/src/graph/type_indexededgelist.c:1947`.
33///
34/// # Examples
35///
36/// ```
37/// use rust_igraph::{Graph, is_same_graph};
38///
39/// // Same edge set, different insertion order ⇒ same graph.
40/// let mut g1 = Graph::with_vertices(3);
41/// g1.add_edge(0, 1).unwrap();
42/// g1.add_edge(1, 2).unwrap();
43/// let mut g2 = Graph::with_vertices(3);
44/// g2.add_edge(1, 2).unwrap();
45/// g2.add_edge(0, 1).unwrap();
46/// assert!(is_same_graph(&g1, &g2));
47///
48/// // Same vcount + ecount but different edge set ⇒ not the same.
49/// let mut g3 = Graph::with_vertices(3);
50/// g3.add_edge(0, 2).unwrap();
51/// g3.add_edge(1, 2).unwrap();
52/// assert!(!is_same_graph(&g1, &g3));
53/// ```
54#[must_use]
55pub fn is_same_graph(g1: &Graph, g2: &Graph) -> bool {
56    if g1.vcount() != g2.vcount() {
57        return false;
58    }
59    if g1.ecount() != g2.ecount() {
60        return false;
61    }
62    if g1.is_directed() != g2.is_directed() {
63        return false;
64    }
65
66    let m = g1.ecount();
67    if m == 0 {
68        return true;
69    }
70
71    // Canonical form: for undirected, `Graph::add_edge` already
72    // stores `from <= to`. For directed, the (from, to) tuple is
73    // already canonical. So lex-sorting the (from, to) pair lists
74    // and comparing element-wise is enough.
75    // Edge count exceeding u32::MAX is impossible given EdgeId is
76    // u32, but if it ever happened fall back to "not the same".
77    let Ok(m_u32) = u32::try_from(m) else {
78        return false;
79    };
80    let e1_result: Result<Vec<(u32, u32)>, _> = (0..m_u32).map(|e| g1.edge(e)).collect();
81    let e2_result: Result<Vec<(u32, u32)>, _> = (0..m_u32).map(|e| g2.edge(e)).collect();
82    let (Ok(mut e1), Ok(mut e2)) = (e1_result, e2_result) else {
83        return false;
84    };
85    // `(u32, u32)` derives `Ord` with lex ordering already; no
86    // custom comparator needed.
87    e1.sort_unstable();
88    e2.sort_unstable();
89    e1 == e2
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::core::Graph;
96
97    #[test]
98    fn same_graph_identical_construction() {
99        let mut g1 = Graph::with_vertices(3);
100        g1.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
101        let mut g2 = Graph::with_vertices(3);
102        g2.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
103        assert!(is_same_graph(&g1, &g2));
104    }
105
106    #[test]
107    fn same_graph_edge_order_does_not_matter() {
108        let mut g1 = Graph::with_vertices(3);
109        g1.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
110        let mut g2 = Graph::with_vertices(3);
111        g2.add_edges(vec![(1u32, 2u32), (0, 1)]).unwrap();
112        assert!(is_same_graph(&g1, &g2));
113    }
114
115    #[test]
116    fn same_graph_undirected_endpoint_orientation_does_not_matter() {
117        let mut g1 = Graph::with_vertices(2);
118        g1.add_edge(0, 1).unwrap();
119        let mut g2 = Graph::with_vertices(2);
120        g2.add_edge(1, 0).unwrap();
121        assert!(is_same_graph(&g1, &g2));
122    }
123
124    #[test]
125    fn different_vcount_not_same() {
126        let mut g1 = Graph::with_vertices(3);
127        g1.add_edge(0, 1).unwrap();
128        let mut g2 = Graph::with_vertices(4);
129        g2.add_edge(0, 1).unwrap();
130        assert!(!is_same_graph(&g1, &g2));
131    }
132
133    #[test]
134    fn different_ecount_not_same() {
135        let mut g1 = Graph::with_vertices(3);
136        g1.add_edge(0, 1).unwrap();
137        let mut g2 = Graph::with_vertices(3);
138        g2.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
139        assert!(!is_same_graph(&g1, &g2));
140    }
141
142    #[test]
143    fn directed_vs_undirected_not_same() {
144        let mut g1 = Graph::new(3, true).unwrap();
145        g1.add_edge(0, 1).unwrap();
146        let mut g2 = Graph::with_vertices(3);
147        g2.add_edge(0, 1).unwrap();
148        assert!(!is_same_graph(&g1, &g2));
149    }
150
151    #[test]
152    fn directed_edge_direction_matters() {
153        // 0→1 and 1→0 are distinct directed edges.
154        let mut g1 = Graph::new(2, true).unwrap();
155        g1.add_edge(0, 1).unwrap();
156        let mut g2 = Graph::new(2, true).unwrap();
157        g2.add_edge(1, 0).unwrap();
158        assert!(!is_same_graph(&g1, &g2));
159    }
160
161    #[test]
162    fn parallel_edges_multiplicity_matters() {
163        // {0-1, 0-1} ≠ {0-1} even though vertex/edge sets look similar.
164        let mut g1 = Graph::with_vertices(2);
165        g1.add_edges(vec![(0u32, 1u32), (0, 1)]).unwrap();
166        let mut g2 = Graph::with_vertices(2);
167        g2.add_edges(vec![(0u32, 1u32), (0, 1)]).unwrap();
168        assert!(is_same_graph(&g1, &g2));
169        // Now drop one parallel edge in g2.
170        let mut g3 = Graph::with_vertices(2);
171        g3.add_edge(0, 1).unwrap();
172        assert!(!is_same_graph(&g1, &g3));
173    }
174
175    #[test]
176    fn self_loop_recognised_as_same() {
177        let mut g1 = Graph::with_vertices(2);
178        g1.add_edges(vec![(0u32, 0u32), (0, 1)]).unwrap();
179        let mut g2 = Graph::with_vertices(2);
180        g2.add_edges(vec![(0u32, 1u32), (0, 0)]).unwrap();
181        assert!(is_same_graph(&g1, &g2));
182    }
183
184    #[test]
185    fn empty_graphs_are_same_when_vcount_matches() {
186        let g1 = Graph::with_vertices(0);
187        let g2 = Graph::with_vertices(0);
188        assert!(is_same_graph(&g1, &g2));
189        let g3 = Graph::with_vertices(5);
190        let g4 = Graph::with_vertices(5);
191        assert!(is_same_graph(&g3, &g4));
192    }
193
194    #[test]
195    fn isomorphic_but_different_edge_sets_not_same() {
196        // Both are paths of length 2 but on different vertex labellings:
197        // g1: 0-1-2; g2: 0-2-1. Isomorphic, not "same".
198        let mut g1 = Graph::with_vertices(3);
199        g1.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
200        let mut g2 = Graph::with_vertices(3);
201        g2.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
202        assert!(!is_same_graph(&g1, &g2));
203    }
204
205    #[test]
206    fn reflexivity() {
207        let mut g = Graph::with_vertices(4);
208        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3)]).unwrap();
209        let g2 = g.clone();
210        assert!(is_same_graph(&g, &g2));
211    }
212}