rust_igraph/algorithms/properties/
independent_set.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_precision_loss,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21pub fn independence_ratio(graph: &Graph) -> IgraphResult<f64> {
36 let n = graph.vcount() as usize;
37 if n == 0 {
38 return Ok(0.0);
39 }
40 let alpha = crate::algorithms::cliques::independence_number(graph)?;
41 Ok(f64::from(alpha) / n as f64)
42}
43
44pub fn greedy_independent_set(graph: &Graph) -> IgraphResult<Vec<u32>> {
66 let n = graph.vcount() as usize;
67 if n == 0 {
68 return Ok(Vec::new());
69 }
70
71 let mut available = vec![true; n];
72 let mut result = Vec::new();
73
74 loop {
75 let mut best_v = None;
76 let mut best_deg = usize::MAX;
77
78 for v in 0..n {
79 if !available[v] {
80 continue;
81 }
82 let deg = graph
83 .neighbors(v as u32)?
84 .iter()
85 .filter(|&&u| available[u as usize])
86 .count();
87 if deg < best_deg {
88 best_deg = deg;
89 best_v = Some(v as u32);
90 }
91 }
92
93 let Some(v) = best_v else { break };
94
95 result.push(v);
96 available[v as usize] = false;
97
98 let nbrs = graph.neighbors(v)?;
99 for &u in &nbrs {
100 available[u as usize] = false;
101 }
102 }
103
104 result.sort_unstable();
105 Ok(result)
106}
107
108#[cfg(test)]
109mod tests {
110 fn is_ind(graph: &Graph, vertices: &[u32]) -> bool {
111 crate::algorithms::properties::is_clique::is_independent_vertex_set(graph, vertices)
112 .unwrap_or(false)
113 }
114
115 use super::*;
116
117 fn path4() -> Graph {
118 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
119 }
120
121 fn k3() -> Graph {
122 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
123 }
124
125 fn k4() -> Graph {
126 Graph::from_edges(
127 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
128 false,
129 Some(4),
130 )
131 .unwrap()
132 }
133
134 fn cycle5() -> Graph {
135 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
136 }
137
138 fn star5() -> Graph {
139 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
140 }
141
142 fn petersen() -> Graph {
143 Graph::from_edges(
144 &[
145 (0, 1),
146 (1, 2),
147 (2, 3),
148 (3, 4),
149 (4, 0),
150 (0, 5),
151 (1, 6),
152 (2, 7),
153 (3, 8),
154 (4, 9),
155 (5, 7),
156 (7, 9),
157 (9, 6),
158 (6, 8),
159 (8, 5),
160 ],
161 false,
162 Some(10),
163 )
164 .unwrap()
165 }
166
167 #[test]
170 fn ir_k3() {
171 let r = independence_ratio(&k3()).unwrap();
172 assert!((r - 1.0 / 3.0).abs() < 0.01);
173 }
174
175 #[test]
176 fn ir_k4() {
177 let r = independence_ratio(&k4()).unwrap();
178 assert!((r - 0.25).abs() < 0.01);
179 }
180
181 #[test]
182 fn ir_path4() {
183 let r = independence_ratio(&path4()).unwrap();
184 assert!((r - 0.5).abs() < 0.01);
185 }
186
187 #[test]
188 fn ir_cycle5() {
189 let r = independence_ratio(&cycle5()).unwrap();
190 assert!((r - 0.4).abs() < 0.01);
191 }
192
193 #[test]
194 fn ir_isolated() {
195 let g = Graph::with_vertices(5);
196 let r = independence_ratio(&g).unwrap();
197 assert!((r - 1.0).abs() < 0.01);
198 }
199
200 #[test]
201 fn ir_empty() {
202 let g = Graph::with_vertices(0);
203 let r = independence_ratio(&g).unwrap();
204 assert!(r.abs() < 1e-10);
205 }
206
207 #[test]
208 fn ir_star5() {
209 let r = independence_ratio(&star5()).unwrap();
210 assert!((r - 0.8).abs() < 0.01);
211 }
212
213 #[test]
216 fn gis_empty() {
217 let g = Graph::with_vertices(0);
218 assert!(greedy_independent_set(&g).unwrap().is_empty());
219 }
220
221 #[test]
222 fn gis_isolated() {
223 let g = Graph::with_vertices(4);
224 let ind = greedy_independent_set(&g).unwrap();
225 assert_eq!(ind.len(), 4);
226 }
227
228 #[test]
229 fn gis_path4() {
230 let g = path4();
231 let ind = greedy_independent_set(&g).unwrap();
232 assert!(is_ind(&g, &ind));
233 assert!(ind.len() >= 2);
234 }
235
236 #[test]
237 fn gis_k4() {
238 let g = k4();
239 let ind = greedy_independent_set(&g).unwrap();
240 assert_eq!(ind.len(), 1);
241 assert!(is_ind(&g, &ind));
242 }
243
244 #[test]
245 fn gis_cycle5() {
246 let g = cycle5();
247 let ind = greedy_independent_set(&g).unwrap();
248 assert!(is_ind(&g, &ind));
249 assert!(ind.len() >= 2);
250 }
251
252 #[test]
253 fn gis_star5() {
254 let g = star5();
255 let ind = greedy_independent_set(&g).unwrap();
256 assert!(is_ind(&g, &ind));
257 assert!(ind.len() >= 2);
258 }
259
260 #[test]
261 fn gis_petersen() {
262 let g = petersen();
263 let ind = greedy_independent_set(&g).unwrap();
264 assert!(is_ind(&g, &ind));
265 assert!(ind.len() >= 2);
266 }
267
268 #[test]
269 fn gis_is_maximal() {
270 let g = petersen();
271 let ind = greedy_independent_set(&g).unwrap();
272 assert!(is_ind(&g, &ind));
273 let n = g.vcount() as usize;
274 let mut in_set = vec![false; n];
275 for &v in &ind {
276 in_set[v as usize] = true;
277 }
278 for v in 0..g.vcount() {
279 if in_set[v as usize] {
280 continue;
281 }
282 let nbrs = g.neighbors(v).unwrap();
283 let has_neighbor_in_set = nbrs.iter().any(|&u| in_set[u as usize]);
284 assert!(
285 has_neighbor_in_set,
286 "vertex {v} could be added — not maximal"
287 );
288 }
289 }
290
291 #[test]
294 fn greedy_at_least_one_for_nonempty() {
295 for n in 1_u32..=5 {
296 let g = Graph::with_vertices(n);
297 let ind = greedy_independent_set(&g).unwrap();
298 assert!(!ind.is_empty());
299 }
300 }
301
302 #[test]
303 fn ratio_between_zero_and_one() {
304 for g in &[path4(), k3(), k4(), cycle5(), star5()] {
305 let r = independence_ratio(g).unwrap();
306 assert!((0.0..=1.0).contains(&r));
307 }
308 }
309
310 #[test]
311 fn greedy_at_most_alpha() {
312 for g in &[path4(), k3(), cycle5()] {
313 let alpha = crate::algorithms::cliques::independence_number(g).unwrap();
314 let ind = greedy_independent_set(g).unwrap();
315 assert!(ind.len() as u32 <= alpha);
316 }
317 }
318}