rust_igraph/algorithms/properties/is_well_covered.rs
1//! Well-covered graph predicate (ALGO-PR-112).
2//!
3//! A graph is well-covered if every maximal independent set has the
4//! same cardinality. Equivalently, every vertex belongs to a maximum
5//! independent set. Complete graphs, edgeless graphs, and `C_5` are
6//! well-covered. Stars with ≥ 3 leaves are not (center alone vs all leaves).
7//!
8//! Directed graphs are treated as undirected (edges lose orientation).
9
10use crate::core::{Graph, IgraphResult};
11
12/// Check whether a graph is well-covered.
13///
14/// A graph is well-covered if all maximal independent sets have the
15/// same cardinality. Uses backtracking enumeration of maximal
16/// independent sets.
17///
18/// Directed graphs are treated as undirected.
19///
20/// # Examples
21///
22/// ```
23/// use rust_igraph::{Graph, is_well_covered};
24///
25/// // Complete graph `K_3`: only maximal IS is any single vertex → well-covered
26/// let mut g = Graph::with_vertices(3);
27/// g.add_edge(0, 1).unwrap();
28/// g.add_edge(1, 2).unwrap();
29/// g.add_edge(2, 0).unwrap();
30/// assert!(is_well_covered(&g).unwrap());
31///
32/// // Star `S_3` is NOT well-covered ({0} size 1, {1,2,3} size 3)
33/// let mut g = Graph::with_vertices(4);
34/// g.add_edge(0, 1).unwrap();
35/// g.add_edge(0, 2).unwrap();
36/// g.add_edge(0, 3).unwrap();
37/// assert!(!is_well_covered(&g).unwrap());
38/// ```
39pub fn is_well_covered(graph: &Graph) -> IgraphResult<bool> {
40 let n = graph.vcount();
41 if n <= 1 {
42 return Ok(true);
43 }
44
45 let n_usize = n as usize;
46 let mut adj = vec![vec![false; n_usize]; n_usize];
47 for v in 0..n {
48 let nbrs = graph.neighbors(v)?;
49 for &w in &nbrs {
50 adj[v as usize][w as usize] = true;
51 adj[w as usize][v as usize] = true;
52 }
53 }
54
55 let mut first_size: Option<usize> = None;
56 let mut current = Vec::new();
57 let mut result = true;
58 let candidates: Vec<usize> = (0..n_usize).collect();
59
60 bron_kerbosch_is(
61 &adj,
62 &mut current,
63 candidates,
64 Vec::new(),
65 &mut first_size,
66 &mut result,
67 );
68
69 Ok(result)
70}
71
72/// Bron-Kerbosch enumeration of maximal independent sets.
73fn bron_kerbosch_is(
74 adj: &[Vec<bool>],
75 current: &mut Vec<usize>,
76 mut candidates: Vec<usize>,
77 mut excluded: Vec<usize>,
78 first_size: &mut Option<usize>,
79 result: &mut bool,
80) {
81 if !*result {
82 return;
83 }
84
85 if candidates.is_empty() && excluded.is_empty() {
86 if !current.is_empty() {
87 match *first_size {
88 None => *first_size = Some(current.len()),
89 Some(sz) => {
90 if current.len() != sz {
91 *result = false;
92 }
93 }
94 }
95 }
96 return;
97 }
98
99 while let Some(v) = candidates.pop() {
100 if !*result {
101 return;
102 }
103 current.push(v);
104
105 let new_cand: Vec<usize> = candidates.iter().copied().filter(|&u| !adj[v][u]).collect();
106 let new_excl: Vec<usize> = excluded.iter().copied().filter(|&u| !adj[v][u]).collect();
107
108 bron_kerbosch_is(adj, current, new_cand, new_excl, first_size, result);
109 current.pop();
110 excluded.push(v);
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn empty_graph() {
120 let g = Graph::with_vertices(0);
121 assert!(is_well_covered(&g).unwrap());
122 }
123
124 #[test]
125 fn single_vertex() {
126 let g = Graph::with_vertices(1);
127 assert!(is_well_covered(&g).unwrap());
128 }
129
130 #[test]
131 fn single_edge() {
132 let mut g = Graph::with_vertices(2);
133 g.add_edge(0, 1).unwrap();
134 assert!(is_well_covered(&g).unwrap());
135 }
136
137 #[test]
138 fn triangle() {
139 // K_3: maximal IS = any single vertex → size 1 → well-covered
140 let mut g = Graph::with_vertices(3);
141 g.add_edge(0, 1).unwrap();
142 g.add_edge(1, 2).unwrap();
143 g.add_edge(2, 0).unwrap();
144 assert!(is_well_covered(&g).unwrap());
145 }
146
147 #[test]
148 fn edgeless_3() {
149 // 3 isolated vertices: only maximal IS is {0,1,2} → well-covered
150 let g = Graph::with_vertices(3);
151 assert!(is_well_covered(&g).unwrap());
152 }
153
154 #[test]
155 fn c4_not_well_covered() {
156 // C_4: maximal IS {0,2} size 2, {1,3} size 2 → well-covered
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 g.add_edge(3, 0).unwrap();
162 assert!(is_well_covered(&g).unwrap());
163 }
164
165 #[test]
166 fn c5_well_covered() {
167 // C_5 is well-covered: all maximal IS have size 2
168 let mut g = Graph::with_vertices(5);
169 g.add_edge(0, 1).unwrap();
170 g.add_edge(1, 2).unwrap();
171 g.add_edge(2, 3).unwrap();
172 g.add_edge(3, 4).unwrap();
173 g.add_edge(4, 0).unwrap();
174 assert!(is_well_covered(&g).unwrap());
175 }
176
177 #[test]
178 fn p4_not_well_covered() {
179 // P_4: 0-1-2-3. Maximal IS: {0,2}, {0,3}, {1,3} all size 2.
180 // But also {0, 2} is maximal. Actually let me re-check:
181 // {0,2} → can add 3? adj[2][3]=true → no. Maximal, size 2.
182 // {0,3} → can add? 1 adj to 0, 2 adj to 3 → maximal, size 2.
183 // {1,3} → can add? 0 adj to 1, 2 adj to 1 and 3 → maximal, size 2.
184 // Hmm, all size 2. Wait, is P_4 well-covered?
185 // Actually P_4 IS well-covered. Let me use P_3 ∪ K_1 instead.
186 // Actually re-checking: P_4 = 0-1-2-3.
187 // Independent sets: {0,2}, {0,3}, {1,3} are maximal of size 2.
188 // Can we get size 1? {1} → can add 3 (not adj to 1) → not maximal.
189 // So all maximal IS have size 2. P_4 IS well-covered.
190 // Let me find a graph that isn't. P_3 with extra vertex:
191 // 0-1-2, 3 isolated. Maximal IS: {0,2,3} size 3, but also
192 // {1,3} size 2 → not well-covered.
193 let mut g = Graph::with_vertices(4);
194 g.add_edge(0, 1).unwrap();
195 g.add_edge(1, 2).unwrap();
196 // vertex 3 is isolated
197 // Maximal IS: {0,2,3} size 3, {1,3} size 2 → not well-covered
198 assert!(!is_well_covered(&g).unwrap());
199 }
200
201 #[test]
202 fn k4() {
203 // K_4: maximal IS = any single vertex → all size 1 → well-covered
204 let mut g = Graph::with_vertices(4);
205 for i in 0..4u32 {
206 for j in (i + 1)..4 {
207 g.add_edge(i, j).unwrap();
208 }
209 }
210 assert!(is_well_covered(&g).unwrap());
211 }
212
213 #[test]
214 fn star_3_not_well_covered() {
215 // Star S_3: center 0, leaves 1,2,3.
216 // Maximal IS: {1,2,3} size 3, {0} size 1 → not well-covered
217 let mut g = Graph::with_vertices(4);
218 g.add_edge(0, 1).unwrap();
219 g.add_edge(0, 2).unwrap();
220 g.add_edge(0, 3).unwrap();
221 assert!(!is_well_covered(&g).unwrap());
222 }
223
224 #[test]
225 fn c7_well_covered() {
226 // C_7 is well-covered: all maximal IS have size 3
227 let mut g = Graph::with_vertices(7);
228 for i in 0..7u32 {
229 g.add_edge(i, (i + 1) % 7).unwrap();
230 }
231 assert!(is_well_covered(&g).unwrap());
232 }
233
234 #[test]
235 fn k33_not_well_covered() {
236 // K_{3,3}: maximal IS are the two sides {0,1,2} and {3,4,5}
237 // both size 3 → well-covered
238 let mut g = Graph::with_vertices(6);
239 for i in 0..3u32 {
240 for j in 3..6u32 {
241 g.add_edge(i, j).unwrap();
242 }
243 }
244 assert!(is_well_covered(&g).unwrap());
245 }
246
247 #[test]
248 fn diamond_not_well_covered() {
249 // Diamond: 0-1, 0-2, 0-3, 1-2, 1-3 (K_4 minus edge 2-3)
250 // Maximal IS: {2,3} size 2. Any single vertex: {0} → can add
251 // 2 or 3? adj[0][2]=true, adj[0][3]=true → {0} maximal? No,
252 // wait: not adj to anything else... Let me re-check.
253 // Actually {0} → is 2 independent of 0? adj[0][2]=true → no.
254 // Is 3 independent? adj[0][3]=true → no. Is 1 independent? adj[0][1]=true → no.
255 // So {0} is maximal of size 1. And {2,3} is maximal of size 2.
256 // → NOT well-covered.
257 let mut g = Graph::with_vertices(4);
258 g.add_edge(0, 1).unwrap();
259 g.add_edge(0, 2).unwrap();
260 g.add_edge(0, 3).unwrap();
261 g.add_edge(1, 2).unwrap();
262 g.add_edge(1, 3).unwrap();
263 assert!(!is_well_covered(&g).unwrap());
264 }
265
266 #[test]
267 fn directed_treated_as_undirected() {
268 // Directed triangle: same as undirected → well-covered
269 let mut g = Graph::new(3, true).unwrap();
270 g.add_edge(0, 1).unwrap();
271 g.add_edge(1, 2).unwrap();
272 g.add_edge(2, 0).unwrap();
273 assert!(is_well_covered(&g).unwrap());
274 }
275}