rust_igraph/algorithms/properties/
is_block.rs1use crate::algorithms::connectivity::biconnected::biconnected_components;
12use crate::algorithms::connectivity::is_connected::{ConnectednessMode, is_connected};
13use crate::core::{Graph, IgraphResult};
14
15pub 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 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 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 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 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 let g = Graph::with_vertices(3);
212 assert!(!is_block_graph(&g).unwrap());
213 }
214
215 #[test]
216 fn triangle_with_pendant() {
217 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 let mut g = Graph::with_vertices(2);
241 g.add_edge(0, 0).unwrap();
242 g.add_edge(0, 1).unwrap();
243 let _ = is_block_graph(&g).unwrap();
245 }
246
247 #[test]
248 fn parallel_edges_not_block() {
249 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 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}