Skip to main content

rust_igraph/algorithms/properties/
is_biregular.rs

1//! Biregular graph predicate (ALGO-PR-126).
2//!
3//! A graph is biregular (also called semiregular bipartite) if it is
4//! bipartite and all vertices in the same partition have the same
5//! degree. The two partitions may have different degrees.
6//!
7//! Every complete bipartite graph `K_{m,n}` is biregular.
8//! Every regular bipartite graph is biregular with both partition
9//! degrees equal.
10//!
11//! Directed graphs are treated as undirected.
12
13use crate::algorithms::properties::is_bipartite::is_bipartite;
14use crate::core::{Graph, IgraphResult};
15
16/// Check whether a graph is biregular.
17///
18/// A bipartite graph is biregular if every vertex in the same
19/// partition has the same degree. Returns `false` for non-bipartite
20/// graphs.
21///
22/// Directed graphs are treated as undirected.
23///
24/// # Examples
25///
26/// ```
27/// use rust_igraph::{Graph, is_biregular};
28///
29/// // K_{2,3} is biregular: left vertices have degree 3, right have degree 2
30/// let mut g = Graph::with_vertices(5);
31/// g.add_edge(0, 2).unwrap();
32/// g.add_edge(0, 3).unwrap();
33/// g.add_edge(0, 4).unwrap();
34/// g.add_edge(1, 2).unwrap();
35/// g.add_edge(1, 3).unwrap();
36/// g.add_edge(1, 4).unwrap();
37/// assert!(is_biregular(&g).unwrap());
38///
39/// // Path P_4: bipartite but not biregular (degrees differ within a side)
40/// let mut g = Graph::with_vertices(4);
41/// g.add_edge(0, 1).unwrap();
42/// g.add_edge(1, 2).unwrap();
43/// g.add_edge(2, 3).unwrap();
44/// assert!(!is_biregular(&g).unwrap());
45/// ```
46pub fn is_biregular(graph: &Graph) -> IgraphResult<bool> {
47    let n = graph.vcount();
48    if n == 0 {
49        return Ok(true);
50    }
51
52    let bp = is_bipartite(graph)?;
53    if !bp.is_bipartite {
54        return Ok(false);
55    }
56
57    let mut deg_false: Option<usize> = None;
58    let mut deg_true: Option<usize> = None;
59
60    for v in 0..n {
61        let d = graph.degree(v)?;
62        let side = bp.types[v as usize];
63        let slot = if side { &mut deg_true } else { &mut deg_false };
64        match *slot {
65            None => *slot = Some(d),
66            Some(expected) => {
67                if d != expected {
68                    return Ok(false);
69                }
70            }
71        }
72    }
73
74    Ok(true)
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn empty_graph() {
83        let g = Graph::with_vertices(0);
84        assert!(is_biregular(&g).unwrap());
85    }
86
87    #[test]
88    fn single_vertex() {
89        let g = Graph::with_vertices(1);
90        assert!(is_biregular(&g).unwrap());
91    }
92
93    #[test]
94    fn single_edge() {
95        let mut g = Graph::with_vertices(2);
96        g.add_edge(0, 1).unwrap();
97        assert!(is_biregular(&g).unwrap());
98    }
99
100    #[test]
101    fn triangle_not_bipartite() {
102        let mut g = Graph::with_vertices(3);
103        g.add_edge(0, 1).unwrap();
104        g.add_edge(1, 2).unwrap();
105        g.add_edge(2, 0).unwrap();
106        assert!(!is_biregular(&g).unwrap());
107    }
108
109    #[test]
110    fn c4_biregular() {
111        // C_4: bipartite, every vertex degree 2
112        let mut g = Graph::with_vertices(4);
113        g.add_edge(0, 1).unwrap();
114        g.add_edge(1, 2).unwrap();
115        g.add_edge(2, 3).unwrap();
116        g.add_edge(3, 0).unwrap();
117        assert!(is_biregular(&g).unwrap());
118    }
119
120    #[test]
121    fn k23_biregular() {
122        // K_{2,3}: left deg=3, right deg=2
123        let mut g = Graph::with_vertices(5);
124        g.add_edge(0, 2).unwrap();
125        g.add_edge(0, 3).unwrap();
126        g.add_edge(0, 4).unwrap();
127        g.add_edge(1, 2).unwrap();
128        g.add_edge(1, 3).unwrap();
129        g.add_edge(1, 4).unwrap();
130        assert!(is_biregular(&g).unwrap());
131    }
132
133    #[test]
134    fn k33_biregular() {
135        // K_{3,3}: regular bipartite → biregular
136        let mut g = Graph::with_vertices(6);
137        for i in 0..3u32 {
138            for j in 3..6u32 {
139                g.add_edge(i, j).unwrap();
140            }
141        }
142        assert!(is_biregular(&g).unwrap());
143    }
144
145    #[test]
146    fn path_p3_biregular() {
147        // P_3: 0-1-2, bipartite {0,2} vs {1}. Deg of {0,2}=1, deg of {1}=2 → biregular
148        let mut g = Graph::with_vertices(3);
149        g.add_edge(0, 1).unwrap();
150        g.add_edge(1, 2).unwrap();
151        assert!(is_biregular(&g).unwrap());
152    }
153
154    #[test]
155    fn path_p4_not_biregular() {
156        // P_4: 0-1-2-3, bipartite {0,2} vs {1,3}. Deg(0)=1, deg(2)=2 → not biregular
157        let mut g = Graph::with_vertices(4);
158        g.add_edge(0, 1).unwrap();
159        g.add_edge(1, 2).unwrap();
160        g.add_edge(2, 3).unwrap();
161        assert!(!is_biregular(&g).unwrap());
162    }
163
164    #[test]
165    fn star_s4_biregular() {
166        // Star S_4: center deg=4, leaves deg=1. Bipartite with each side uniform.
167        let mut g = Graph::with_vertices(5);
168        g.add_edge(0, 1).unwrap();
169        g.add_edge(0, 2).unwrap();
170        g.add_edge(0, 3).unwrap();
171        g.add_edge(0, 4).unwrap();
172        assert!(is_biregular(&g).unwrap());
173    }
174
175    #[test]
176    fn edgeless_biregular() {
177        // All isolated vertices: bipartite, all degrees 0
178        let g = Graph::with_vertices(5);
179        assert!(is_biregular(&g).unwrap());
180    }
181
182    #[test]
183    fn disconnected_biregular() {
184        // Two disjoint K_{1,2}: each star has center deg=2, leaves deg=1
185        let mut g = Graph::with_vertices(6);
186        g.add_edge(0, 1).unwrap();
187        g.add_edge(0, 2).unwrap();
188        g.add_edge(3, 4).unwrap();
189        g.add_edge(3, 5).unwrap();
190        assert!(is_biregular(&g).unwrap());
191    }
192
193    #[test]
194    fn disconnected_not_biregular() {
195        // K_{1,1} + K_{1,2}: one side has deg 1 and 2
196        let mut g = Graph::with_vertices(5);
197        g.add_edge(0, 1).unwrap();
198        g.add_edge(2, 3).unwrap();
199        g.add_edge(2, 4).unwrap();
200        // Side false: {0, 2} with degrees 1 and 2 → not biregular
201        // (depends on bipartition assignment, but regardless, mixed degrees on one side)
202        // Actually, the bipartition may put them on different sides.
203        // Let's think: component 0-1: sides {0} and {1}. Component 2-3-4: sides {2} and {3,4}.
204        // BFS from 0: 0→false, 1→true. BFS from 2: 2→false, 3→true, 4→true.
205        // false side: {0, 2} with deg 1, 2. Not biregular.
206        assert!(!is_biregular(&g).unwrap());
207    }
208
209    #[test]
210    fn cube_q3_biregular() {
211        // Q_3 (3-cube): 3-regular bipartite → biregular
212        let mut g = Graph::with_vertices(8);
213        let edges = [
214            (0, 1),
215            (0, 2),
216            (0, 4),
217            (1, 3),
218            (1, 5),
219            (2, 3),
220            (2, 6),
221            (3, 7),
222            (4, 5),
223            (4, 6),
224            (5, 7),
225            (6, 7),
226        ];
227        for (u, v) in edges {
228            g.add_edge(u, v).unwrap();
229        }
230        assert!(is_biregular(&g).unwrap());
231    }
232
233    #[test]
234    fn directed_treated_as_undirected() {
235        let mut g = Graph::new(4, true).unwrap();
236        g.add_edge(0, 1).unwrap();
237        g.add_edge(2, 1).unwrap();
238        g.add_edge(2, 3).unwrap();
239        g.add_edge(0, 3).unwrap();
240        // Undirected view: C_4 bipartite, all deg 2 → biregular
241        assert!(is_biregular(&g).unwrap());
242    }
243}