Skip to main content

rust_igraph/algorithms/properties/
satisfies_ore.rs

1//! Ore's condition for Hamiltonicity (ALGO-PR-119).
2//!
3//! A simple undirected graph on n ≥ 3 vertices satisfies Ore's
4//! condition if for every pair of non-adjacent vertices u, v:
5//! deg(u) + deg(v) ≥ n. By Ore's theorem (1960), such a graph is
6//! Hamiltonian. Ore's condition is weaker than Dirac's (every graph
7//! satisfying Dirac also satisfies Ore, but not vice versa).
8//!
9//! Returns `false` for directed graphs or n < 3.
10
11use crate::core::{Graph, IgraphResult};
12
13/// Check whether a graph satisfies Ore's condition.
14///
15/// Ore's condition: for every pair of non-adjacent vertices u, v,
16/// deg(u) + deg(v) ≥ n. A graph satisfying this on n ≥ 3 vertices
17/// is guaranteed to be Hamiltonian.
18///
19/// Returns `false` for directed graphs or n < 3.
20///
21/// # Examples
22///
23/// ```
24/// use rust_igraph::{Graph, satisfies_ore};
25///
26/// // `K_4`: every non-adjacent pair... but K_4 is complete (no
27/// // non-adjacent pairs), so Ore is vacuously true
28/// let mut g = Graph::with_vertices(4);
29/// for i in 0..4u32 {
30///     for j in (i+1)..4 {
31///         g.add_edge(i, j).unwrap();
32///     }
33/// }
34/// assert!(satisfies_ore(&g).unwrap());
35///
36/// // Path `P_4`: endpoints 0,3 are non-adjacent, deg(0)+deg(3)=1+1=2 < 4
37/// let mut g = Graph::with_vertices(4);
38/// g.add_edge(0, 1).unwrap();
39/// g.add_edge(1, 2).unwrap();
40/// g.add_edge(2, 3).unwrap();
41/// assert!(!satisfies_ore(&g).unwrap());
42/// ```
43pub fn satisfies_ore(graph: &Graph) -> IgraphResult<bool> {
44    if graph.is_directed() {
45        return Ok(false);
46    }
47
48    let n = graph.vcount();
49    if n < 3 {
50        return Ok(false);
51    }
52
53    let n_usize = n as usize;
54
55    // Precompute degrees
56    let mut degrees = Vec::with_capacity(n_usize);
57    for v in 0..n {
58        degrees.push(graph.degree(v)?);
59    }
60
61    // Build adjacency for non-adjacency check
62    let mut adj = vec![vec![false; n_usize]; n_usize];
63    for v in 0..n {
64        let nbrs = graph.neighbors(v)?;
65        for &w in &nbrs {
66            adj[v as usize][w as usize] = true;
67        }
68    }
69
70    // Check all non-adjacent pairs
71    for u in 0..n_usize {
72        for v in (u + 1)..n_usize {
73            if !adj[u][v] && degrees[u] + degrees[v] < n_usize {
74                return Ok(false);
75            }
76        }
77    }
78
79    Ok(true)
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn empty_graph() {
88        let g = Graph::with_vertices(0);
89        assert!(!satisfies_ore(&g).unwrap());
90    }
91
92    #[test]
93    fn single_vertex() {
94        let g = Graph::with_vertices(1);
95        assert!(!satisfies_ore(&g).unwrap());
96    }
97
98    #[test]
99    fn two_vertices() {
100        let mut g = Graph::with_vertices(2);
101        g.add_edge(0, 1).unwrap();
102        assert!(!satisfies_ore(&g).unwrap());
103    }
104
105    #[test]
106    fn triangle() {
107        // K_3: complete, no non-adjacent pairs → vacuously true
108        let mut g = Graph::with_vertices(3);
109        g.add_edge(0, 1).unwrap();
110        g.add_edge(1, 2).unwrap();
111        g.add_edge(2, 0).unwrap();
112        assert!(satisfies_ore(&g).unwrap());
113    }
114
115    #[test]
116    fn complete_k4() {
117        let mut g = Graph::with_vertices(4);
118        for i in 0..4u32 {
119            for j in (i + 1)..4 {
120                g.add_edge(i, j).unwrap();
121            }
122        }
123        assert!(satisfies_ore(&g).unwrap());
124    }
125
126    #[test]
127    fn c4() {
128        // C_4: non-adjacent pairs (0,2) and (1,3).
129        // deg(0)+deg(2) = 2+2 = 4 ≥ 4 ✓. Same for (1,3). → satisfies Ore
130        let mut g = Graph::with_vertices(4);
131        g.add_edge(0, 1).unwrap();
132        g.add_edge(1, 2).unwrap();
133        g.add_edge(2, 3).unwrap();
134        g.add_edge(3, 0).unwrap();
135        assert!(satisfies_ore(&g).unwrap());
136    }
137
138    #[test]
139    fn c5_fails() {
140        // C_5: each non-adjacent pair has deg sum 2+2=4 < 5 → fails
141        let mut g = Graph::with_vertices(5);
142        g.add_edge(0, 1).unwrap();
143        g.add_edge(1, 2).unwrap();
144        g.add_edge(2, 3).unwrap();
145        g.add_edge(3, 4).unwrap();
146        g.add_edge(4, 0).unwrap();
147        assert!(!satisfies_ore(&g).unwrap());
148    }
149
150    #[test]
151    fn path_p4_fails() {
152        // P_4: endpoints deg 1, non-adjacent, sum 1+1=2 < 4 → fails
153        let mut g = Graph::with_vertices(4);
154        g.add_edge(0, 1).unwrap();
155        g.add_edge(1, 2).unwrap();
156        g.add_edge(2, 3).unwrap();
157        assert!(!satisfies_ore(&g).unwrap());
158    }
159
160    #[test]
161    fn star_fails() {
162        let mut g = Graph::with_vertices(5);
163        g.add_edge(0, 1).unwrap();
164        g.add_edge(0, 2).unwrap();
165        g.add_edge(0, 3).unwrap();
166        g.add_edge(0, 4).unwrap();
167        // Non-adj pair (1,2): deg 1+1=2 < 5 → fails
168        assert!(!satisfies_ore(&g).unwrap());
169    }
170
171    #[test]
172    fn directed_returns_false() {
173        let mut g = Graph::new(3, true).unwrap();
174        g.add_edge(0, 1).unwrap();
175        g.add_edge(1, 2).unwrap();
176        g.add_edge(2, 0).unwrap();
177        assert!(!satisfies_ore(&g).unwrap());
178    }
179
180    #[test]
181    fn k33_satisfies() {
182        // K_{3,3}: non-adj pairs within same part, deg 3+3=6 ≥ 6 ✓
183        let mut g = Graph::with_vertices(6);
184        for i in 0..3u32 {
185            for j in 3..6u32 {
186                g.add_edge(i, j).unwrap();
187            }
188        }
189        assert!(satisfies_ore(&g).unwrap());
190    }
191
192    #[test]
193    fn ore_but_not_dirac() {
194        // Graph where Ore holds but Dirac fails: n=4, vertex 0 has
195        // degree 1 (fails Dirac), but every non-adjacent pair has
196        // sufficient degree sum.
197        // 0-1, 1-2, 2-3, 1-3. Non-adj pairs: (0,2) deg 1+2=3<4 → fails.
198        // Hard to construct Ore-but-not-Dirac on small n.
199        // Try: K_4 minus one edge: 0-1, 0-2, 0-3, 1-2, 1-3, missing 2-3.
200        // deg(0)=3, deg(1)=3, deg(2)=2, deg(3)=2. Only non-adj pair: (2,3).
201        // 2+2=4 ≥ 4 ✓. Dirac: min deg=2 ≥ 4/2=2 ✓. Both hold.
202        // Let me try n=5, K5 minus two edges: miss (3,4) and (2,4).
203        // 0-1,0-2,0-3,0-4,1-2,1-3,1-4,2-3. deg: 0→4,1→4,2→3,3→3,4→2.
204        // Non-adj: (2,4) sum 3+2=5≥5 ✓, (3,4) sum 3+2=5≥5 ✓. Ore ✓.
205        // Dirac: min deg=2 < 5/2=3 → fails. Ore-not-Dirac!
206        let mut g = Graph::with_vertices(5);
207        g.add_edge(0, 1).unwrap();
208        g.add_edge(0, 2).unwrap();
209        g.add_edge(0, 3).unwrap();
210        g.add_edge(0, 4).unwrap();
211        g.add_edge(1, 2).unwrap();
212        g.add_edge(1, 3).unwrap();
213        g.add_edge(1, 4).unwrap();
214        g.add_edge(2, 3).unwrap();
215        assert!(satisfies_ore(&g).unwrap());
216    }
217
218    #[test]
219    fn edgeless_fails() {
220        let g = Graph::with_vertices(4);
221        assert!(!satisfies_ore(&g).unwrap());
222    }
223
224    #[test]
225    fn p3_fails() {
226        // P_3 (n=3): 0-1-2. Non-adj pair (0,2): deg 1+1=2 < 3 → fails
227        let mut g = Graph::with_vertices(3);
228        g.add_edge(0, 1).unwrap();
229        g.add_edge(1, 2).unwrap();
230        assert!(!satisfies_ore(&g).unwrap());
231    }
232}