Skip to main content

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}