rust_igraph/algorithms/properties/
is_pseudo_forest.rs1use crate::algorithms::connectivity::components::connected_components;
15use crate::core::{Graph, IgraphResult};
16
17pub fn is_pseudo_forest(graph: &Graph) -> IgraphResult<bool> {
50 if graph.is_directed() {
51 return Ok(false);
52 }
53
54 let n = graph.vcount();
55 if n == 0 {
56 return Ok(true);
57 }
58
59 let cc = connected_components(graph)?;
60 let comp_count = cc.count as usize;
61
62 let mut comp_verts = vec![0u64; comp_count];
63 let mut comp_edges = vec![0u64; comp_count];
64
65 for v in 0..n {
66 let c = cc.membership[v as usize] as usize;
67 comp_verts[c] = comp_verts[c].saturating_add(1);
68 }
69
70 let ecount = graph.ecount();
71 for eid in 0..ecount {
72 let eid_u32 = u32::try_from(eid).unwrap_or(u32::MAX);
73 let (u, _v) = graph.edge(eid_u32)?;
74 let c = cc.membership[u as usize] as usize;
75 comp_edges[c] = comp_edges[c].saturating_add(1);
76 }
77
78 for c in 0..comp_count {
79 if comp_edges[c] > comp_verts[c] {
80 return Ok(false);
81 }
82 }
83
84 Ok(true)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn empty_graph() {
93 let g = Graph::with_vertices(0);
94 assert!(is_pseudo_forest(&g).unwrap());
95 }
96
97 #[test]
98 fn single_vertex() {
99 let g = Graph::with_vertices(1);
100 assert!(is_pseudo_forest(&g).unwrap());
101 }
102
103 #[test]
104 fn edgeless_graph() {
105 let g = Graph::with_vertices(5);
106 assert!(is_pseudo_forest(&g).unwrap());
107 }
108
109 #[test]
110 fn single_edge() {
111 let mut g = Graph::with_vertices(2);
112 g.add_edge(0, 1).unwrap();
113 assert!(is_pseudo_forest(&g).unwrap());
114 }
115
116 #[test]
117 fn path_is_pseudo_forest() {
118 let mut g = Graph::with_vertices(4);
119 g.add_edge(0, 1).unwrap();
120 g.add_edge(1, 2).unwrap();
121 g.add_edge(2, 3).unwrap();
122 assert!(is_pseudo_forest(&g).unwrap());
123 }
124
125 #[test]
126 fn tree_is_pseudo_forest() {
127 let mut g = Graph::with_vertices(5);
128 g.add_edge(0, 1).unwrap();
129 g.add_edge(0, 2).unwrap();
130 g.add_edge(1, 3).unwrap();
131 g.add_edge(1, 4).unwrap();
132 assert!(is_pseudo_forest(&g).unwrap());
133 }
134
135 #[test]
136 fn triangle_is_pseudo_forest() {
137 let mut g = Graph::with_vertices(3);
138 g.add_edge(0, 1).unwrap();
139 g.add_edge(1, 2).unwrap();
140 g.add_edge(2, 0).unwrap();
141 assert!(is_pseudo_forest(&g).unwrap());
142 }
143
144 #[test]
145 fn cycle_4_is_pseudo_forest() {
146 let mut g = Graph::with_vertices(4);
147 g.add_edge(0, 1).unwrap();
148 g.add_edge(1, 2).unwrap();
149 g.add_edge(2, 3).unwrap();
150 g.add_edge(3, 0).unwrap();
151 assert!(is_pseudo_forest(&g).unwrap());
152 }
153
154 #[test]
155 fn unicyclic_with_tail() {
156 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, 0).unwrap();
161 g.add_edge(2, 3).unwrap();
162 g.add_edge(3, 4).unwrap();
163 assert!(is_pseudo_forest(&g).unwrap());
164 }
165
166 #[test]
167 fn two_disjoint_cycles() {
168 let mut g = Graph::with_vertices(6);
170 g.add_edge(0, 1).unwrap();
171 g.add_edge(1, 2).unwrap();
172 g.add_edge(2, 0).unwrap();
173 g.add_edge(3, 4).unwrap();
174 g.add_edge(4, 5).unwrap();
175 g.add_edge(5, 3).unwrap();
176 assert!(is_pseudo_forest(&g).unwrap());
177 }
178
179 #[test]
180 fn k4_not_pseudo_forest() {
181 let mut g = Graph::with_vertices(4);
183 for u in 0..4u32 {
184 for v in (u + 1)..4 {
185 g.add_edge(u, v).unwrap();
186 }
187 }
188 assert!(!is_pseudo_forest(&g).unwrap());
189 }
190
191 #[test]
192 fn diamond_not_pseudo_forest() {
193 let mut g = Graph::with_vertices(4);
195 g.add_edge(0, 1).unwrap();
196 g.add_edge(0, 2).unwrap();
197 g.add_edge(0, 3).unwrap();
198 g.add_edge(1, 2).unwrap();
199 g.add_edge(1, 3).unwrap();
200 assert!(!is_pseudo_forest(&g).unwrap());
201 }
202
203 #[test]
204 fn theta_graph_not_pseudo_forest() {
205 let mut g = Graph::with_vertices(4);
208 g.add_edge(0, 1).unwrap();
209 g.add_edge(0, 2).unwrap();
210 g.add_edge(2, 1).unwrap();
211 g.add_edge(0, 3).unwrap();
212 g.add_edge(3, 1).unwrap();
213 assert!(!is_pseudo_forest(&g).unwrap());
214 }
215
216 #[test]
217 fn directed_returns_false() {
218 let mut g = Graph::new(3, true).unwrap();
219 g.add_edge(0, 1).unwrap();
220 g.add_edge(1, 2).unwrap();
221 assert!(!is_pseudo_forest(&g).unwrap());
222 }
223
224 #[test]
225 fn self_loop_counts() {
226 let mut g = Graph::with_vertices(1);
228 g.add_edge(0, 0).unwrap();
229 assert!(is_pseudo_forest(&g).unwrap());
230 }
231
232 #[test]
233 fn two_self_loops_not_pseudo_forest() {
234 let mut g = Graph::with_vertices(1);
236 g.add_edge(0, 0).unwrap();
237 g.add_edge(0, 0).unwrap();
238 assert!(!is_pseudo_forest(&g).unwrap());
239 }
240
241 #[test]
242 fn parallel_edges_not_pseudo_forest() {
243 let mut g = Graph::with_vertices(2);
245 g.add_edge(0, 1).unwrap();
246 g.add_edge(0, 1).unwrap();
247 g.add_edge(0, 1).unwrap();
248 assert!(!is_pseudo_forest(&g).unwrap());
249 }
250
251 #[test]
252 fn parallel_edge_is_pseudo_forest() {
253 let mut g = Graph::with_vertices(2);
255 g.add_edge(0, 1).unwrap();
256 g.add_edge(0, 1).unwrap();
257 assert!(is_pseudo_forest(&g).unwrap());
258 }
259
260 #[test]
261 fn mixed_components() {
262 let mut g = Graph::with_vertices(7);
265 g.add_edge(0, 1).unwrap();
266 g.add_edge(1, 2).unwrap();
267 for u in 3..7u32 {
268 for v in (u + 1)..7 {
269 g.add_edge(u, v).unwrap();
270 }
271 }
272 assert!(!is_pseudo_forest(&g).unwrap());
273 }
274}