Skip to main content

rust_igraph/algorithms/properties/
is_tournament.rs

1//! Tournament graph predicate (ALGO-PR-084).
2//!
3//! A tournament is a directed graph in which every pair of distinct
4//! vertices is connected by exactly one directed edge (either u→v or
5//! v→u, but not both).
6//!
7//! Equivalently: directed, simple, no mutual edges, and exactly
8//! n(n-1)/2 edges.
9
10use crate::algorithms::properties::is_simple::is_simple;
11use crate::algorithms::properties::mutual::has_mutual;
12use crate::core::{Graph, IgraphResult};
13
14/// Check whether a graph is a tournament.
15///
16/// A tournament is a complete asymmetric directed graph: for every
17/// pair of distinct vertices, exactly one directed edge exists.
18///
19/// Returns `false` for undirected graphs.
20/// Returns `true` for the empty graph and single-vertex graph (vacuously).
21///
22/// # Examples
23///
24/// ```
25/// use rust_igraph::{Graph, is_tournament};
26///
27/// // Directed 3-cycle: 0→1, 1→2, 2→0 — a tournament
28/// let mut g = Graph::new(3, true).unwrap();
29/// g.add_edge(0, 1).unwrap();
30/// g.add_edge(1, 2).unwrap();
31/// g.add_edge(2, 0).unwrap();
32/// assert!(is_tournament(&g).unwrap());
33///
34/// // Undirected triangle is NOT a tournament
35/// let mut g = Graph::with_vertices(3);
36/// g.add_edge(0, 1).unwrap();
37/// g.add_edge(1, 2).unwrap();
38/// g.add_edge(2, 0).unwrap();
39/// assert!(!is_tournament(&g).unwrap());
40/// ```
41pub fn is_tournament(graph: &Graph) -> IgraphResult<bool> {
42    if !graph.is_directed() {
43        return Ok(false);
44    }
45
46    let n = graph.vcount();
47    if n <= 1 {
48        return Ok(true);
49    }
50
51    // n(n-1)/2 edges required
52    let n_u64 = u64::from(n);
53    let expected = n_u64.saturating_mul(n_u64.saturating_sub(1)) / 2;
54    if graph.ecount() as u64 != expected {
55        return Ok(false);
56    }
57
58    if !is_simple(graph)? {
59        return Ok(false);
60    }
61
62    // No mutual (reciprocal) edges allowed
63    if has_mutual(graph, false)? {
64        return Ok(false);
65    }
66
67    Ok(true)
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn empty_graph() {
76        let g = Graph::new(0, true).unwrap();
77        assert!(is_tournament(&g).unwrap());
78    }
79
80    #[test]
81    fn single_vertex() {
82        let g = Graph::new(1, true).unwrap();
83        assert!(is_tournament(&g).unwrap());
84    }
85
86    #[test]
87    fn single_arc() {
88        // 0→1: tournament on 2 vertices
89        let mut g = Graph::new(2, true).unwrap();
90        g.add_edge(0, 1).unwrap();
91        assert!(is_tournament(&g).unwrap());
92    }
93
94    #[test]
95    fn directed_3cycle() {
96        // 0→1→2→0: tournament
97        let mut g = Graph::new(3, true).unwrap();
98        g.add_edge(0, 1).unwrap();
99        g.add_edge(1, 2).unwrap();
100        g.add_edge(2, 0).unwrap();
101        assert!(is_tournament(&g).unwrap());
102    }
103
104    #[test]
105    fn transitive_tournament_3() {
106        // 0→1, 0→2, 1→2: transitive tournament on 3 vertices
107        let mut g = Graph::new(3, true).unwrap();
108        g.add_edge(0, 1).unwrap();
109        g.add_edge(0, 2).unwrap();
110        g.add_edge(1, 2).unwrap();
111        assert!(is_tournament(&g).unwrap());
112    }
113
114    #[test]
115    fn transitive_tournament_4() {
116        // Total order: 0→1, 0→2, 0→3, 1→2, 1→3, 2→3
117        let mut g = Graph::new(4, true).unwrap();
118        g.add_edge(0, 1).unwrap();
119        g.add_edge(0, 2).unwrap();
120        g.add_edge(0, 3).unwrap();
121        g.add_edge(1, 2).unwrap();
122        g.add_edge(1, 3).unwrap();
123        g.add_edge(2, 3).unwrap();
124        assert!(is_tournament(&g).unwrap());
125    }
126
127    #[test]
128    fn undirected_returns_false() {
129        let mut g = Graph::with_vertices(3);
130        g.add_edge(0, 1).unwrap();
131        g.add_edge(1, 2).unwrap();
132        g.add_edge(2, 0).unwrap();
133        assert!(!is_tournament(&g).unwrap());
134    }
135
136    #[test]
137    fn mutual_edge_not_tournament() {
138        // 0↔1, 0→2, 1→2: has mutual edge → not tournament
139        let mut g = Graph::new(3, true).unwrap();
140        g.add_edge(0, 1).unwrap();
141        g.add_edge(1, 0).unwrap();
142        g.add_edge(0, 2).unwrap();
143        g.add_edge(1, 2).unwrap();
144        // 4 edges, expected 3 → fails edge count
145        assert!(!is_tournament(&g).unwrap());
146    }
147
148    #[test]
149    fn missing_edge_not_tournament() {
150        // 0→1, 1→2 but no edge between 0 and 2: only 2 of 3 required
151        let mut g = Graph::new(3, true).unwrap();
152        g.add_edge(0, 1).unwrap();
153        g.add_edge(1, 2).unwrap();
154        assert!(!is_tournament(&g).unwrap());
155    }
156
157    #[test]
158    fn self_loop_not_tournament() {
159        // 0→1, 1→0→0 (self-loop): not simple → not tournament
160        let mut g = Graph::new(2, true).unwrap();
161        g.add_edge(0, 1).unwrap();
162        g.add_edge(0, 0).unwrap();
163        // 2 edges but expected 1 → fails
164        assert!(!is_tournament(&g).unwrap());
165    }
166
167    #[test]
168    fn complete_directed_not_tournament() {
169        // Both directions for every pair: n(n-1) edges, not n(n-1)/2
170        let mut g = Graph::new(3, true).unwrap();
171        g.add_edge(0, 1).unwrap();
172        g.add_edge(1, 0).unwrap();
173        g.add_edge(0, 2).unwrap();
174        g.add_edge(2, 0).unwrap();
175        g.add_edge(1, 2).unwrap();
176        g.add_edge(2, 1).unwrap();
177        assert!(!is_tournament(&g).unwrap());
178    }
179
180    #[test]
181    fn two_vertices_no_edge() {
182        let g = Graph::new(2, true).unwrap();
183        assert!(!is_tournament(&g).unwrap());
184    }
185
186    #[test]
187    fn tournament_5_vertices() {
188        // 5-vertex tournament: 0→1, 0→2, 0→3, 0→4, 1→2, 2→3, 3→4, 4→1, 1→3, 4→2
189        let mut g = Graph::new(5, true).unwrap();
190        g.add_edge(0, 1).unwrap();
191        g.add_edge(0, 2).unwrap();
192        g.add_edge(0, 3).unwrap();
193        g.add_edge(0, 4).unwrap();
194        g.add_edge(1, 2).unwrap();
195        g.add_edge(2, 3).unwrap();
196        g.add_edge(3, 4).unwrap();
197        g.add_edge(4, 1).unwrap();
198        g.add_edge(1, 3).unwrap();
199        g.add_edge(4, 2).unwrap();
200        assert!(is_tournament(&g).unwrap());
201    }
202}