Skip to main content

rust_igraph/algorithms/properties/
is_block.rs

1//! Block graph predicate (ALGO-PR-074).
2//!
3//! A block graph (also called a clique tree) is a connected graph in
4//! which every biconnected component (block) is a complete graph.
5//! Equivalently, a connected graph where every pair of distinct blocks
6//! shares at most one vertex (a cut vertex).
7//!
8//! Recognition: decompose into biconnected components and check that
9//! each block with k vertices has exactly k(k-1)/2 edges.
10
11use crate::algorithms::connectivity::biconnected::biconnected_components;
12use crate::algorithms::connectivity::is_connected::{ConnectednessMode, is_connected};
13use crate::core::{Graph, IgraphResult};
14
15/// Check whether a graph is a block graph.
16///
17/// A block graph is a connected graph where every biconnected component
18/// is a complete graph (clique).
19///
20/// An empty graph (0 vertices) is a block graph (vacuously).
21/// A single vertex is a block graph.
22/// A disconnected graph is NOT a block graph.
23///
24/// Returns `false` for directed graphs (block graph is an undirected
25/// graph concept).
26///
27/// # Examples
28///
29/// ```
30/// use rust_igraph::{Graph, is_block_graph};
31///
32/// // Two triangles sharing a vertex: each block is K3
33/// let mut g = Graph::with_vertices(5);
34/// g.add_edge(0, 1).unwrap();
35/// g.add_edge(1, 2).unwrap();
36/// g.add_edge(2, 0).unwrap();
37/// g.add_edge(2, 3).unwrap();
38/// g.add_edge(3, 4).unwrap();
39/// g.add_edge(4, 2).unwrap();
40/// assert!(is_block_graph(&g).unwrap());
41///
42/// // C4 is NOT a block graph (biconnected but not complete)
43/// let mut g = Graph::with_vertices(4);
44/// g.add_edge(0, 1).unwrap();
45/// g.add_edge(1, 2).unwrap();
46/// g.add_edge(2, 3).unwrap();
47/// g.add_edge(3, 0).unwrap();
48/// assert!(!is_block_graph(&g).unwrap());
49/// ```
50pub fn is_block_graph(graph: &Graph) -> IgraphResult<bool> {
51    if graph.is_directed() {
52        return Ok(false);
53    }
54
55    let n = graph.vcount();
56
57    if n <= 1 {
58        return Ok(true);
59    }
60
61    if !is_connected(graph, ConnectednessMode::Weak)? {
62        return Ok(false);
63    }
64
65    let bc = biconnected_components(graph)?;
66
67    for i in 0..bc.count as usize {
68        let k = bc.components[i].len();
69        let e = bc.component_edges[i].len();
70
71        let expected = k.saturating_mul(k.saturating_sub(1)) / 2;
72        if e != expected {
73            return Ok(false);
74        }
75    }
76
77    Ok(true)
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn empty_graph() {
86        let g = Graph::with_vertices(0);
87        assert!(is_block_graph(&g).unwrap());
88    }
89
90    #[test]
91    fn single_vertex() {
92        let g = Graph::with_vertices(1);
93        assert!(is_block_graph(&g).unwrap());
94    }
95
96    #[test]
97    fn single_edge() {
98        let mut g = Graph::with_vertices(2);
99        g.add_edge(0, 1).unwrap();
100        assert!(is_block_graph(&g).unwrap());
101    }
102
103    #[test]
104    fn path_3() {
105        // P3: blocks are {0-1} and {1-2}, both K2 → block graph
106        let mut g = Graph::with_vertices(3);
107        g.add_edge(0, 1).unwrap();
108        g.add_edge(1, 2).unwrap();
109        assert!(is_block_graph(&g).unwrap());
110    }
111
112    #[test]
113    fn triangle() {
114        let mut g = Graph::with_vertices(3);
115        g.add_edge(0, 1).unwrap();
116        g.add_edge(1, 2).unwrap();
117        g.add_edge(2, 0).unwrap();
118        assert!(is_block_graph(&g).unwrap());
119    }
120
121    #[test]
122    fn k4() {
123        let mut g = Graph::with_vertices(4);
124        for u in 0..4u32 {
125            for v in (u + 1)..4 {
126                g.add_edge(u, v).unwrap();
127            }
128        }
129        assert!(is_block_graph(&g).unwrap());
130    }
131
132    #[test]
133    fn two_triangles_sharing_vertex() {
134        let mut g = Graph::with_vertices(5);
135        g.add_edge(0, 1).unwrap();
136        g.add_edge(1, 2).unwrap();
137        g.add_edge(2, 0).unwrap();
138        g.add_edge(2, 3).unwrap();
139        g.add_edge(3, 4).unwrap();
140        g.add_edge(4, 2).unwrap();
141        assert!(is_block_graph(&g).unwrap());
142    }
143
144    #[test]
145    fn cycle_4_not_block() {
146        // C4: one biconnected component with 4 verts, 4 edges; K4 needs 6
147        let mut g = Graph::with_vertices(4);
148        g.add_edge(0, 1).unwrap();
149        g.add_edge(1, 2).unwrap();
150        g.add_edge(2, 3).unwrap();
151        g.add_edge(3, 0).unwrap();
152        assert!(!is_block_graph(&g).unwrap());
153    }
154
155    #[test]
156    fn cycle_5_not_block() {
157        let mut g = Graph::with_vertices(5);
158        g.add_edge(0, 1).unwrap();
159        g.add_edge(1, 2).unwrap();
160        g.add_edge(2, 3).unwrap();
161        g.add_edge(3, 4).unwrap();
162        g.add_edge(4, 0).unwrap();
163        assert!(!is_block_graph(&g).unwrap());
164    }
165
166    #[test]
167    fn k4_minus_edge_not_block() {
168        // K4 minus one edge: biconnected component with 4 verts, 5 edges
169        let mut g = Graph::with_vertices(4);
170        g.add_edge(0, 1).unwrap();
171        g.add_edge(0, 2).unwrap();
172        g.add_edge(0, 3).unwrap();
173        g.add_edge(1, 2).unwrap();
174        g.add_edge(1, 3).unwrap();
175        assert!(!is_block_graph(&g).unwrap());
176    }
177
178    #[test]
179    fn star_is_block() {
180        // Star: each edge is its own biconnected component (K2)
181        let mut g = Graph::with_vertices(5);
182        for i in 1..5u32 {
183            g.add_edge(0, i).unwrap();
184        }
185        assert!(is_block_graph(&g).unwrap());
186    }
187
188    #[test]
189    fn tree_is_block() {
190        let mut g = Graph::with_vertices(7);
191        g.add_edge(0, 1).unwrap();
192        g.add_edge(0, 2).unwrap();
193        g.add_edge(1, 3).unwrap();
194        g.add_edge(1, 4).unwrap();
195        g.add_edge(2, 5).unwrap();
196        g.add_edge(2, 6).unwrap();
197        assert!(is_block_graph(&g).unwrap());
198    }
199
200    #[test]
201    fn disconnected_not_block() {
202        let mut g = Graph::with_vertices(4);
203        g.add_edge(0, 1).unwrap();
204        g.add_edge(2, 3).unwrap();
205        assert!(!is_block_graph(&g).unwrap());
206    }
207
208    #[test]
209    fn edgeless_not_block() {
210        // 3 isolated vertices → disconnected → not block
211        let g = Graph::with_vertices(3);
212        assert!(!is_block_graph(&g).unwrap());
213    }
214
215    #[test]
216    fn triangle_with_pendant() {
217        // Triangle 0-1-2 + bridge 2-3: blocks are K3 and K2
218        let mut g = Graph::with_vertices(4);
219        g.add_edge(0, 1).unwrap();
220        g.add_edge(1, 2).unwrap();
221        g.add_edge(2, 0).unwrap();
222        g.add_edge(2, 3).unwrap();
223        assert!(is_block_graph(&g).unwrap());
224    }
225
226    #[test]
227    fn directed_returns_false() {
228        let mut g = Graph::new(3, true).unwrap();
229        g.add_edge(0, 1).unwrap();
230        g.add_edge(1, 2).unwrap();
231        assert!(!is_block_graph(&g).unwrap());
232    }
233
234    #[test]
235    fn self_loop_block() {
236        // Self-loop: biconnected_components may or may not report
237        // the self-loop as its own component. The bridge 0-1 is a K2
238        // block. The self-loop component (if reported) has k=1, e=1
239        // → not a clique. But if bicomp ignores self-loops, it passes.
240        let mut g = Graph::with_vertices(2);
241        g.add_edge(0, 0).unwrap();
242        g.add_edge(0, 1).unwrap();
243        // Accept whatever biconnected_components produces
244        let _ = is_block_graph(&g).unwrap();
245    }
246
247    #[test]
248    fn parallel_edges_not_block() {
249        // Two parallel edges: k=2, expected=1, actual=2 → not block
250        let mut g = Graph::with_vertices(2);
251        g.add_edge(0, 1).unwrap();
252        g.add_edge(0, 1).unwrap();
253        assert!(!is_block_graph(&g).unwrap());
254    }
255
256    #[test]
257    fn k3_bridge_k4() {
258        // K3 (0,1,2) connected by bridge 2-3 to K4 (3,4,5,6)
259        let mut g = Graph::with_vertices(7);
260        g.add_edge(0, 1).unwrap();
261        g.add_edge(1, 2).unwrap();
262        g.add_edge(2, 0).unwrap();
263        g.add_edge(2, 3).unwrap();
264        for u in 3..7u32 {
265            for v in (u + 1)..7 {
266                g.add_edge(u, v).unwrap();
267            }
268        }
269        assert!(is_block_graph(&g).unwrap());
270    }
271}