Skip to main content

rust_igraph/algorithms/properties/
is_chain_graph.rs

1//! Chain graph predicate (ALGO-PR-114).
2//!
3//! A bipartite graph is a chain graph if the neighborhoods of the
4//! vertices in each part are totally ordered by inclusion.
5//! Equivalently, a bipartite graph with no induced `2K_2`
6//! (two disjoint edges as induced subgraph).
7//!
8//! Non-bipartite and directed graphs return `false`.
9
10use crate::algorithms::properties::is_bipartite::is_bipartite;
11use crate::core::{Graph, IgraphResult};
12
13/// Check whether a graph is a chain graph.
14///
15/// A bipartite graph is a chain graph if the neighborhoods of the
16/// vertices in each part are totally ordered by inclusion.
17/// Returns `false` for non-bipartite or directed graphs.
18///
19/// # Examples
20///
21/// ```
22/// use rust_igraph::{Graph, is_chain_graph};
23///
24/// // Complete bipartite `K_{2,3}` is a chain graph
25/// let mut g = Graph::with_vertices(5);
26/// g.add_edge(0, 2).unwrap();
27/// g.add_edge(0, 3).unwrap();
28/// g.add_edge(0, 4).unwrap();
29/// g.add_edge(1, 2).unwrap();
30/// g.add_edge(1, 3).unwrap();
31/// g.add_edge(1, 4).unwrap();
32/// assert!(is_chain_graph(&g).unwrap());
33///
34/// // `C_6` is bipartite but NOT a chain graph (has induced `2K_2`)
35/// let mut g = Graph::with_vertices(6);
36/// g.add_edge(0, 1).unwrap();
37/// g.add_edge(1, 2).unwrap();
38/// g.add_edge(2, 3).unwrap();
39/// g.add_edge(3, 4).unwrap();
40/// g.add_edge(4, 5).unwrap();
41/// g.add_edge(5, 0).unwrap();
42/// assert!(!is_chain_graph(&g).unwrap());
43/// ```
44pub fn is_chain_graph(graph: &Graph) -> IgraphResult<bool> {
45    if graph.is_directed() {
46        return Ok(false);
47    }
48
49    let n = graph.vcount();
50    if n <= 2 {
51        return is_bipartite(graph).map(|r| r.is_bipartite);
52    }
53
54    let bip = is_bipartite(graph)?;
55    if !bip.is_bipartite {
56        return Ok(false);
57    }
58
59    let n_usize = n as usize;
60    let types = &bip.types;
61
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    // Partition vertices by type
71    let part_a: Vec<usize> = (0..n_usize).filter(|&v| !types[v]).collect();
72    let part_b: Vec<usize> = (0..n_usize).filter(|&v| types[v]).collect();
73
74    // Check neighborhoods in part A are totally ordered by inclusion
75    // w.r.t. their neighbors in part B
76    if !neighborhoods_nested(&adj, &part_a, &part_b) {
77        return Ok(false);
78    }
79
80    // Check neighborhoods in part B are totally ordered by inclusion
81    // w.r.t. their neighbors in part A
82    if !neighborhoods_nested(&adj, &part_b, &part_a) {
83        return Ok(false);
84    }
85
86    Ok(true)
87}
88
89/// Check that the neighborhoods of vertices in `part` (restricted to
90/// `other_part`) are totally ordered by inclusion.
91fn neighborhoods_nested(adj: &[Vec<bool>], part: &[usize], other_part: &[usize]) -> bool {
92    if part.len() <= 1 {
93        return true;
94    }
95
96    // Collect neighborhood sets (as sorted vecs of other-side indices)
97    let mut nbr_sets: Vec<Vec<usize>> = part
98        .iter()
99        .map(|&v| other_part.iter().copied().filter(|&u| adj[v][u]).collect())
100        .collect();
101
102    // Sort by neighborhood size
103    nbr_sets.sort_by_key(Vec::len);
104
105    // Check each consecutive pair: smaller must be subset of larger
106    for window in nbr_sets.windows(2) {
107        let smaller = &window[0];
108        let larger = &window[1];
109        if !is_subset(smaller, larger) {
110            return false;
111        }
112    }
113
114    true
115}
116
117/// Check if `a` ⊆ `b` (both sorted).
118fn is_subset(a: &[usize], b: &[usize]) -> bool {
119    if a.len() > b.len() {
120        return false;
121    }
122    let mut bi = 0;
123    for &x in a {
124        while bi < b.len() && b[bi] < x {
125            bi += 1;
126        }
127        if bi >= b.len() || b[bi] != x {
128            return false;
129        }
130        bi += 1;
131    }
132    true
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn empty_graph() {
141        let g = Graph::with_vertices(0);
142        assert!(is_chain_graph(&g).unwrap());
143    }
144
145    #[test]
146    fn single_vertex() {
147        let g = Graph::with_vertices(1);
148        assert!(is_chain_graph(&g).unwrap());
149    }
150
151    #[test]
152    fn single_edge() {
153        let mut g = Graph::with_vertices(2);
154        g.add_edge(0, 1).unwrap();
155        assert!(is_chain_graph(&g).unwrap());
156    }
157
158    #[test]
159    fn path_p3() {
160        // 0-1-2: parts {0,2} and {1}. N(0)={1}, N(2)={1} → equal → nested.
161        let mut g = Graph::with_vertices(3);
162        g.add_edge(0, 1).unwrap();
163        g.add_edge(1, 2).unwrap();
164        assert!(is_chain_graph(&g).unwrap());
165    }
166
167    #[test]
168    fn path_p4() {
169        // 0-1-2-3: parts {0,2} and {1,3}. N(0)={1}, N(2)={1,3} → {1}⊂{1,3} ✓
170        // N(1)={0,2}, N(3)={2} → {2}⊂{0,2} ✓ → chain graph
171        let mut g = Graph::with_vertices(4);
172        g.add_edge(0, 1).unwrap();
173        g.add_edge(1, 2).unwrap();
174        g.add_edge(2, 3).unwrap();
175        assert!(is_chain_graph(&g).unwrap());
176    }
177
178    #[test]
179    fn complete_bipartite_k23() {
180        let mut g = Graph::with_vertices(5);
181        g.add_edge(0, 2).unwrap();
182        g.add_edge(0, 3).unwrap();
183        g.add_edge(0, 4).unwrap();
184        g.add_edge(1, 2).unwrap();
185        g.add_edge(1, 3).unwrap();
186        g.add_edge(1, 4).unwrap();
187        assert!(is_chain_graph(&g).unwrap());
188    }
189
190    #[test]
191    fn c4_not_chain() {
192        // C_4: parts {0,2}, {1,3}. N(0)={1,3}, N(2)={1,3} → equal → nested ✓
193        // N(1)={0,2}, N(3)={0,2} → equal → nested ✓
194        // Actually C_4 IS a chain graph (all nbrs same size and equal).
195        let mut g = Graph::with_vertices(4);
196        g.add_edge(0, 1).unwrap();
197        g.add_edge(1, 2).unwrap();
198        g.add_edge(2, 3).unwrap();
199        g.add_edge(3, 0).unwrap();
200        assert!(is_chain_graph(&g).unwrap());
201    }
202
203    #[test]
204    fn c6_not_chain() {
205        // C_6 has induced 2K_2 → NOT a chain graph
206        let mut g = Graph::with_vertices(6);
207        g.add_edge(0, 1).unwrap();
208        g.add_edge(1, 2).unwrap();
209        g.add_edge(2, 3).unwrap();
210        g.add_edge(3, 4).unwrap();
211        g.add_edge(4, 5).unwrap();
212        g.add_edge(5, 0).unwrap();
213        assert!(!is_chain_graph(&g).unwrap());
214    }
215
216    #[test]
217    fn star() {
218        // Star: parts {0} and {1,2,3}. N(0)={1,2,3} → single vertex → nested.
219        // N(1)=N(2)=N(3)={0} → all equal → nested. Chain graph.
220        let mut g = Graph::with_vertices(4);
221        g.add_edge(0, 1).unwrap();
222        g.add_edge(0, 2).unwrap();
223        g.add_edge(0, 3).unwrap();
224        assert!(is_chain_graph(&g).unwrap());
225    }
226
227    #[test]
228    fn two_disjoint_edges() {
229        // 2K_2: 0-1, 2-3. Parts {0,2}, {1,3}. N(0)={1}, N(2)={3}.
230        // Neither {1}⊂{3} nor {3}⊂{1} → NOT nested → NOT chain.
231        let mut g = Graph::with_vertices(4);
232        g.add_edge(0, 1).unwrap();
233        g.add_edge(2, 3).unwrap();
234        assert!(!is_chain_graph(&g).unwrap());
235    }
236
237    #[test]
238    fn triangle_not_bipartite() {
239        let mut g = Graph::with_vertices(3);
240        g.add_edge(0, 1).unwrap();
241        g.add_edge(1, 2).unwrap();
242        g.add_edge(2, 0).unwrap();
243        assert!(!is_chain_graph(&g).unwrap());
244    }
245
246    #[test]
247    fn directed_returns_false() {
248        let mut g = Graph::new(2, true).unwrap();
249        g.add_edge(0, 1).unwrap();
250        assert!(!is_chain_graph(&g).unwrap());
251    }
252
253    #[test]
254    fn isolated_vertices() {
255        // 3 isolated vertices: bipartite, each has empty neighborhood → nested.
256        let g = Graph::with_vertices(3);
257        assert!(is_chain_graph(&g).unwrap());
258    }
259
260    #[test]
261    fn k33_not_chain() {
262        // K_{3,3}: all vertices in each part have the same neighborhood → nested ✓
263        let mut g = Graph::with_vertices(6);
264        for i in 0..3u32 {
265            for j in 3..6u32 {
266                g.add_edge(i, j).unwrap();
267            }
268        }
269        assert!(is_chain_graph(&g).unwrap());
270    }
271
272    #[test]
273    fn bipartite_with_induced_2k2() {
274        // 0-2, 0-3, 1-3 (missing 1-2). Parts {0,1}, {2,3}.
275        // N(0)={2,3}, N(1)={3}. {3}⊂{2,3} ✓
276        // N(2)={0}, N(3)={0,1}. {0}⊂{0,1} ✓
277        // Actually this IS a chain graph! No induced 2K_2.
278        // Let me try: 0-2, 1-3 only.
279        // Parts {0,1}, {2,3}. N(0)={2}, N(1)={3}. Neither subset → NOT chain.
280        let mut g = Graph::with_vertices(4);
281        g.add_edge(0, 2).unwrap();
282        g.add_edge(1, 3).unwrap();
283        assert!(!is_chain_graph(&g).unwrap());
284    }
285
286    #[test]
287    fn nested_neighborhoods() {
288        // 0-3, 0-4, 1-3, 1-4, 1-5, 2-3, 2-4, 2-5
289        // Parts {0,1,2}, {3,4,5}
290        // N(0)={3,4}, N(1)={3,4,5}, N(2)={3,4,5}
291        // Sorted: {3,4} ⊂ {3,4,5} = {3,4,5} ✓
292        // N(3)={0,1,2}, N(4)={0,1,2}, N(5)={1,2}
293        // Sorted: {1,2} ⊂ {0,1,2} = {0,1,2} ✓
294        let mut g = Graph::with_vertices(6);
295        g.add_edge(0, 3).unwrap();
296        g.add_edge(0, 4).unwrap();
297        g.add_edge(1, 3).unwrap();
298        g.add_edge(1, 4).unwrap();
299        g.add_edge(1, 5).unwrap();
300        g.add_edge(2, 3).unwrap();
301        g.add_edge(2, 4).unwrap();
302        g.add_edge(2, 5).unwrap();
303        assert!(is_chain_graph(&g).unwrap());
304    }
305}