Skip to main content

rust_igraph/algorithms/properties/
is_self_complementary.rs

1//! Self-complementary graph predicate (ALGO-PR-076).
2//!
3//! A graph G is self-complementary if it is isomorphic to its
4//! complement. Self-complementary graphs have n(n-1)/4 edges (so n
5//! must be 0 or 1 mod 4), and include P4, C5, and the Paley graphs.
6//!
7//! Recognition: build the complement and test isomorphism via VF2.
8//! Returns `false` for directed graphs (the complement is only
9//! well-defined for simple undirected graphs in this context).
10
11use crate::algorithms::isomorphism::vf2::isomorphic_vf2;
12use crate::algorithms::operators::complementer::complementer;
13use crate::core::{Graph, IgraphResult};
14
15/// Check whether a graph is self-complementary.
16///
17/// A graph is self-complementary if it is isomorphic to its complement.
18///
19/// Returns `false` for directed graphs. Handles simple undirected
20/// graphs; for multigraphs or graphs with self-loops, the complement
21/// is computed without loops.
22///
23/// An empty graph (0 vertices) is self-complementary (vacuously).
24/// A single vertex is self-complementary.
25///
26/// # Examples
27///
28/// ```
29/// use rust_igraph::{Graph, is_self_complementary};
30///
31/// // P4 (0-1-2-3) is self-complementary
32/// let mut g = Graph::with_vertices(4);
33/// g.add_edge(0, 1).unwrap();
34/// g.add_edge(1, 2).unwrap();
35/// g.add_edge(2, 3).unwrap();
36/// assert!(is_self_complementary(&g).unwrap());
37///
38/// // Triangle K3 is NOT self-complementary
39/// let mut g = Graph::with_vertices(3);
40/// g.add_edge(0, 1).unwrap();
41/// g.add_edge(1, 2).unwrap();
42/// g.add_edge(2, 0).unwrap();
43/// assert!(!is_self_complementary(&g).unwrap());
44/// ```
45pub fn is_self_complementary(graph: &Graph) -> IgraphResult<bool> {
46    if graph.is_directed() {
47        return Ok(false);
48    }
49
50    let n = graph.vcount();
51    if n <= 1 {
52        return Ok(true);
53    }
54
55    let n64 = u64::from(n);
56    let total_possible = n64.saturating_mul(n64.saturating_sub(1)) / 2;
57    let ecount = graph.ecount() as u64;
58    if total_possible % 2 != 0 || ecount != total_possible / 2 {
59        return Ok(false);
60    }
61
62    let comp = complementer(graph, false)?;
63    let result = isomorphic_vf2(graph, &comp, None, None, None, None)?;
64
65    Ok(result.iso)
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn empty_graph() {
74        let g = Graph::with_vertices(0);
75        assert!(is_self_complementary(&g).unwrap());
76    }
77
78    #[test]
79    fn single_vertex() {
80        let g = Graph::with_vertices(1);
81        assert!(is_self_complementary(&g).unwrap());
82    }
83
84    #[test]
85    fn k2_not_self_complementary() {
86        // K2: 1 edge, complement has 0 edges, not isomorphic
87        let mut g = Graph::with_vertices(2);
88        g.add_edge(0, 1).unwrap();
89        assert!(!is_self_complementary(&g).unwrap());
90    }
91
92    #[test]
93    fn k3_not_self_complementary() {
94        let mut g = Graph::with_vertices(3);
95        g.add_edge(0, 1).unwrap();
96        g.add_edge(1, 2).unwrap();
97        g.add_edge(2, 0).unwrap();
98        assert!(!is_self_complementary(&g).unwrap());
99    }
100
101    #[test]
102    fn path_4_is_self_complementary() {
103        // P4: 0-1-2-3, complement: 0-2, 0-3, 1-3 → isomorphic to P4
104        let mut g = Graph::with_vertices(4);
105        g.add_edge(0, 1).unwrap();
106        g.add_edge(1, 2).unwrap();
107        g.add_edge(2, 3).unwrap();
108        assert!(is_self_complementary(&g).unwrap());
109    }
110
111    #[test]
112    fn cycle_5_is_self_complementary() {
113        // C5: 5 edges, complement is also C5 (Petersen inner star)
114        let mut g = Graph::with_vertices(5);
115        g.add_edge(0, 1).unwrap();
116        g.add_edge(1, 2).unwrap();
117        g.add_edge(2, 3).unwrap();
118        g.add_edge(3, 4).unwrap();
119        g.add_edge(4, 0).unwrap();
120        assert!(is_self_complementary(&g).unwrap());
121    }
122
123    #[test]
124    fn cycle_4_not_self_complementary() {
125        // C4: 4 edges, n(n-1)/2 = 6, need 3 edges for self-comp → wrong edge count
126        let mut g = Graph::with_vertices(4);
127        g.add_edge(0, 1).unwrap();
128        g.add_edge(1, 2).unwrap();
129        g.add_edge(2, 3).unwrap();
130        g.add_edge(3, 0).unwrap();
131        assert!(!is_self_complementary(&g).unwrap());
132    }
133
134    #[test]
135    fn edgeless_2_not_self_complementary() {
136        // 2 vertices, 0 edges: complement has 1 edge → not iso
137        let g = Graph::with_vertices(2);
138        assert!(!is_self_complementary(&g).unwrap());
139    }
140
141    #[test]
142    fn edgeless_4_not_self_complementary() {
143        // 4 vertices, 0 edges: need 3 edges → wrong count
144        let g = Graph::with_vertices(4);
145        assert!(!is_self_complementary(&g).unwrap());
146    }
147
148    #[test]
149    fn k4_not_self_complementary() {
150        // K4: 6 edges, need 3 → wrong count
151        let mut g = Graph::with_vertices(4);
152        for u in 0..4u32 {
153            for v in (u + 1)..4 {
154                g.add_edge(u, v).unwrap();
155            }
156        }
157        assert!(!is_self_complementary(&g).unwrap());
158    }
159
160    #[test]
161    fn star_k13_not_self_complementary() {
162        // Star on 4 vertices: 3 edges = n(n-1)/4 = 3 → right count
163        // But star is not isomorphic to its complement (which is K3 + isolated)
164        // Wait: complement of star K_{1,3} on 4 verts is triangle on {1,2,3}
165        // with vertex 0 isolated. Star has degree sequence [3,1,1,1],
166        // complement has [0,2,2,2]. Not isomorphic.
167        let mut g = Graph::with_vertices(4);
168        g.add_edge(0, 1).unwrap();
169        g.add_edge(0, 2).unwrap();
170        g.add_edge(0, 3).unwrap();
171        assert!(!is_self_complementary(&g).unwrap());
172    }
173
174    #[test]
175    fn directed_returns_false() {
176        let mut g = Graph::new(4, true).unwrap();
177        g.add_edge(0, 1).unwrap();
178        g.add_edge(1, 2).unwrap();
179        g.add_edge(2, 3).unwrap();
180        assert!(!is_self_complementary(&g).unwrap());
181    }
182
183    #[test]
184    fn three_vertices_not_possible() {
185        // n=3: n(n-1)/2 = 3, 3/2 is not integer → no self-comp graph on 3 verts
186        // P3: 2 edges ≠ 1.5 → early false
187        let mut g = Graph::with_vertices(3);
188        g.add_edge(0, 1).unwrap();
189        g.add_edge(1, 2).unwrap();
190        assert!(!is_self_complementary(&g).unwrap());
191    }
192
193    #[test]
194    fn bull_graph_is_self_complementary() {
195        // Bull: triangle 0-1-2 + pendants 0-3, 1-4: complement is also a bull
196        let mut g = Graph::with_vertices(5);
197        g.add_edge(0, 1).unwrap();
198        g.add_edge(1, 2).unwrap();
199        g.add_edge(2, 0).unwrap();
200        g.add_edge(0, 3).unwrap();
201        g.add_edge(1, 4).unwrap();
202        assert!(is_self_complementary(&g).unwrap());
203    }
204}