Skip to main content

rust_igraph/algorithms/properties/
is_complete_multipartite.rs

1//! Complete multipartite graph predicate (ALGO-PR-090).
2//!
3//! A complete multipartite graph `K_{n1,n2,...,nk}` has vertices
4//! partitioned into k independent sets (parts), with every pair of
5//! vertices in different parts connected by an edge. The complement
6//! of a complete multipartite graph is a disjoint union of cliques.
7//!
8//! For directed graphs, the function returns `false`.
9
10use crate::algorithms::connectivity::connected_components;
11use crate::algorithms::operators::complementer::complementer;
12use crate::algorithms::properties::is_simple::is_simple;
13use crate::core::{Graph, IgraphResult};
14
15/// Check whether a graph is a complete multipartite graph.
16///
17/// A complete multipartite graph has vertices partitioned into
18/// independent sets where every two vertices in different parts are
19/// adjacent. Equivalently, the complement is a disjoint union of
20/// cliques.
21///
22/// Returns `false` for directed graphs and non-simple graphs.
23/// Returns `true` for the empty graph (vacuously), single vertex,
24/// complete graphs (single part of size 0 with all vertices in other
25/// parts, or equivalently k parts of size 1), and edgeless graphs
26/// (single part containing all vertices).
27///
28/// If the graph is complete multipartite, returns `Some(parts)` where
29/// `parts` is a sorted vector of part sizes. Returns `None` otherwise.
30///
31/// # Examples
32///
33/// ```
34/// use rust_igraph::{Graph, is_complete_multipartite};
35///
36/// // K_{2,3}: complete bipartite
37/// let mut g = Graph::with_vertices(5);
38/// for i in 0..2u32 {
39///     for j in 2..5u32 {
40///         g.add_edge(i, j).unwrap();
41///     }
42/// }
43/// let parts = is_complete_multipartite(&g).unwrap();
44/// assert_eq!(parts, Some(vec![2, 3]));
45///
46/// // Triangle K_3 = K_{1,1,1}
47/// let mut g = Graph::with_vertices(3);
48/// g.add_edge(0, 1).unwrap();
49/// g.add_edge(1, 2).unwrap();
50/// g.add_edge(2, 0).unwrap();
51/// let parts = is_complete_multipartite(&g).unwrap();
52/// assert_eq!(parts, Some(vec![1, 1, 1]));
53/// ```
54pub fn is_complete_multipartite(graph: &Graph) -> IgraphResult<Option<Vec<u32>>> {
55    if graph.is_directed() {
56        return Ok(None);
57    }
58
59    let n = graph.vcount();
60    if n == 0 {
61        return Ok(Some(vec![]));
62    }
63
64    if !is_simple(graph)? {
65        return Ok(None);
66    }
67
68    // Edgeless graph: one part containing all vertices
69    if graph.ecount() == 0 {
70        return Ok(Some(vec![n]));
71    }
72
73    // Complement of a complete multipartite graph is a disjoint union of cliques.
74    let comp = complementer(graph, false)?;
75    let comps = connected_components(&comp)?;
76
77    let membership = &comps.membership;
78    let num_comps = comps.count as usize;
79
80    // Collect vertices per component
81    let mut parts: Vec<Vec<u32>> = vec![vec![]; num_comps];
82    for (v, &comp_id) in membership.iter().enumerate() {
83        parts[comp_id as usize].push(u32::try_from(v).unwrap_or(u32::MAX));
84    }
85
86    // Verify each part is a clique in the complement (= independent set in original)
87    for part in &parts {
88        if part.len() > 1 {
89            // Check that this set is a clique in the complement
90            let subgraph_comp = is_clique_in_graph(&comp, part)?;
91            if !subgraph_comp {
92                return Ok(None);
93            }
94        }
95    }
96
97    // Verify total edge count matches expected for complete multipartite
98    let mut expected_edges: u64 = 0;
99    let part_sizes: Vec<u32> = parts
100        .iter()
101        .map(|p| u32::try_from(p.len()).unwrap_or(u32::MAX))
102        .collect();
103    let total_n = u64::from(n);
104    for &size in &part_sizes {
105        let s = u64::from(size);
106        // Each vertex in this part connects to all vertices NOT in this part
107        expected_edges = expected_edges.saturating_add(s.saturating_mul(total_n.saturating_sub(s)));
108    }
109    // Each edge counted twice (once from each endpoint's part)
110    expected_edges /= 2;
111
112    if graph.ecount() as u64 != expected_edges {
113        return Ok(None);
114    }
115
116    let mut result: Vec<u32> = part_sizes;
117    result.sort_unstable();
118    Ok(Some(result))
119}
120
121fn is_clique_in_graph(graph: &Graph, vertices: &[u32]) -> IgraphResult<bool> {
122    let k = vertices.len();
123    if k <= 1 {
124        return Ok(true);
125    }
126
127    for (i, &vi) in vertices.iter().enumerate() {
128        let nbrs = graph.neighbors(vi)?;
129        let count = vertices
130            .iter()
131            .enumerate()
132            .filter(|&(j, &vj)| i != j && nbrs.contains(&vj))
133            .count();
134        if count != k - 1 {
135            return Ok(false);
136        }
137    }
138    Ok(true)
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn empty_graph() {
147        let g = Graph::with_vertices(0);
148        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![]));
149    }
150
151    #[test]
152    fn single_vertex() {
153        let g = Graph::with_vertices(1);
154        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![1]));
155    }
156
157    #[test]
158    fn edgeless_3() {
159        let g = Graph::with_vertices(3);
160        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![3]));
161    }
162
163    #[test]
164    fn single_edge_k11() {
165        let mut g = Graph::with_vertices(2);
166        g.add_edge(0, 1).unwrap();
167        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![1, 1]));
168    }
169
170    #[test]
171    fn triangle_k111() {
172        let mut g = Graph::with_vertices(3);
173        g.add_edge(0, 1).unwrap();
174        g.add_edge(1, 2).unwrap();
175        g.add_edge(2, 0).unwrap();
176        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![1, 1, 1]));
177    }
178
179    #[test]
180    fn k23() {
181        let mut g = Graph::with_vertices(5);
182        for i in 0..2u32 {
183            for j in 2..5u32 {
184                g.add_edge(i, j).unwrap();
185            }
186        }
187        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![2, 3]));
188    }
189
190    #[test]
191    fn k22() {
192        let mut g = Graph::with_vertices(4);
193        g.add_edge(0, 2).unwrap();
194        g.add_edge(0, 3).unwrap();
195        g.add_edge(1, 2).unwrap();
196        g.add_edge(1, 3).unwrap();
197        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![2, 2]));
198    }
199
200    #[test]
201    fn k_1_1_1_1() {
202        // K4 = K_{1,1,1,1}
203        let mut g = Graph::with_vertices(4);
204        g.add_edge(0, 1).unwrap();
205        g.add_edge(0, 2).unwrap();
206        g.add_edge(0, 3).unwrap();
207        g.add_edge(1, 2).unwrap();
208        g.add_edge(1, 3).unwrap();
209        g.add_edge(2, 3).unwrap();
210        assert_eq!(
211            is_complete_multipartite(&g).unwrap(),
212            Some(vec![1, 1, 1, 1])
213        );
214    }
215
216    #[test]
217    fn star_k13() {
218        // K_{1,3}: center 0 connected to 1,2,3
219        let mut g = Graph::with_vertices(4);
220        g.add_edge(0, 1).unwrap();
221        g.add_edge(0, 2).unwrap();
222        g.add_edge(0, 3).unwrap();
223        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![1, 3]));
224    }
225
226    #[test]
227    fn k_2_2_2() {
228        // Complete tripartite K_{2,2,2}: 6 vertices, parts {0,1}, {2,3}, {4,5}
229        let mut g = Graph::with_vertices(6);
230        let parts = [vec![0u32, 1], vec![2, 3], vec![4, 5]];
231        for i in 0..3 {
232            for j in (i + 1)..3 {
233                for &u in &parts[i] {
234                    for &v in &parts[j] {
235                        g.add_edge(u, v).unwrap();
236                    }
237                }
238            }
239        }
240        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![2, 2, 2]));
241    }
242
243    #[test]
244    fn path_p3_is_k12() {
245        // P3: 0-1-2 = K_{1,2} (star with 2 leaves)
246        let mut g = Graph::with_vertices(3);
247        g.add_edge(0, 1).unwrap();
248        g.add_edge(1, 2).unwrap();
249        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![1, 2]));
250    }
251
252    #[test]
253    fn path_p4_not_multipartite() {
254        // P4: 0-1-2-3 — vertex 0 and 2 are non-adjacent but in different
255        // parts, yet 1 and 2 are adjacent in the same "should-be" part
256        let mut g = Graph::with_vertices(4);
257        g.add_edge(0, 1).unwrap();
258        g.add_edge(1, 2).unwrap();
259        g.add_edge(2, 3).unwrap();
260        assert_eq!(is_complete_multipartite(&g).unwrap(), None);
261    }
262
263    #[test]
264    fn cycle_c4_is_k22() {
265        // C4 = K_{2,2}
266        let mut g = Graph::with_vertices(4);
267        g.add_edge(0, 1).unwrap();
268        g.add_edge(1, 2).unwrap();
269        g.add_edge(2, 3).unwrap();
270        g.add_edge(3, 0).unwrap();
271        assert_eq!(is_complete_multipartite(&g).unwrap(), Some(vec![2, 2]));
272    }
273
274    #[test]
275    fn cycle_c5_not_multipartite() {
276        let mut g = Graph::with_vertices(5);
277        g.add_edge(0, 1).unwrap();
278        g.add_edge(1, 2).unwrap();
279        g.add_edge(2, 3).unwrap();
280        g.add_edge(3, 4).unwrap();
281        g.add_edge(4, 0).unwrap();
282        assert_eq!(is_complete_multipartite(&g).unwrap(), None);
283    }
284
285    #[test]
286    fn directed_returns_none() {
287        let mut g = Graph::new(3, true).unwrap();
288        g.add_edge(0, 1).unwrap();
289        g.add_edge(1, 2).unwrap();
290        g.add_edge(2, 0).unwrap();
291        assert_eq!(is_complete_multipartite(&g).unwrap(), None);
292    }
293
294    #[test]
295    fn disconnected_not_multipartite() {
296        let mut g = Graph::with_vertices(4);
297        g.add_edge(0, 1).unwrap();
298        g.add_edge(2, 3).unwrap();
299        assert_eq!(is_complete_multipartite(&g).unwrap(), None);
300    }
301
302    #[test]
303    fn petersen_not_multipartite() {
304        // Petersen graph is 3-regular on 10 vertices, not complete multipartite
305        let mut g = Graph::with_vertices(10);
306        // Outer cycle
307        for i in 0..5u32 {
308            g.add_edge(i, (i + 1) % 5).unwrap();
309        }
310        // Inner pentagram
311        for i in 0..5u32 {
312            g.add_edge(i + 5, 5 + (i + 2) % 5).unwrap();
313        }
314        // Spokes
315        for i in 0..5u32 {
316            g.add_edge(i, i + 5).unwrap();
317        }
318        assert_eq!(is_complete_multipartite(&g).unwrap(), None);
319    }
320}