Skip to main content

problemreductions/models/graph/
biclique_cover.rs

1//! Biclique Cover problem implementation.
2//!
3//! Given a bipartite graph `G = (L ∪ R, E)` and a bound `k`, find up to `k`
4//! bicliques whose union covers every edge of `G`. Each biclique must be a
5//! *complete bipartite subgraph of `G`* — if `L_b ⊆ L` and `R_b ⊆ R` describe
6//! biclique `b`, every pair `(l, r) ∈ L_b × R_b` must itself be an edge of `G`.
7//! A biclique is *not* simply any pair `(L_b, R_b)`; the subgraph it induces
8//! has to be complete. The objective minimizes the total number of vertex
9//! memberships across all bicliques.
10//!
11//! Under this classical definition, the minimum `k` for which a rank-`k`
12//! biclique cover exists is exactly the _Boolean rank_ of the biadjacency
13//! matrix of `G` (Monson, Pullman, Rees 1995), matching exact Boolean
14//! Matrix Factorization.
15
16use crate::registry::{FieldInfo, ProblemSchemaEntry};
17use crate::topology::BipartiteGraph;
18use crate::traits::Problem;
19use crate::types::Min;
20use serde::{Deserialize, Serialize};
21use std::collections::HashSet;
22
23inventory::submit! {
24    ProblemSchemaEntry {
25        name: "BicliqueCover",
26        display_name: "Biclique Cover",
27        aliases: &[],
28        dimensions: &[],
29        module_path: module_path!(),
30        description: "Cover bipartite edges with k bicliques",
31        fields: &[
32            FieldInfo { name: "left_size", type_name: "usize", description: "Vertices in left partition" },
33            FieldInfo { name: "right_size", type_name: "usize", description: "Vertices in right partition" },
34            FieldInfo { name: "edges", type_name: "Vec<(usize, usize)>", description: "Bipartite edges" },
35            FieldInfo { name: "k", type_name: "usize", description: "Number of bicliques" },
36        ],
37    }
38}
39
40/// The Biclique Cover problem.
41///
42/// Given a bipartite graph with vertex sets L and R, find k bicliques
43/// that together cover all edges. Each vertex can be in any subset of the k bicliques.
44///
45/// # Example
46///
47/// ```
48/// use problemreductions::models::graph::BicliqueCover;
49/// use problemreductions::topology::BipartiteGraph;
50/// use problemreductions::{Problem, Solver, BruteForce};
51///
52/// // Bipartite graph: L = {0, 1}, R = {0, 1}
53/// // Edges: (0,0), (0,1), (1,0) in bipartite-local coordinates
54/// let graph = BipartiteGraph::new(2, 2, vec![(0, 0), (0, 1), (1, 0)]);
55/// let problem = BicliqueCover::new(graph, 2);
56///
57/// let solver = BruteForce::new();
58/// let solutions = solver.find_all_witnesses(&problem);
59///
60/// // Check coverage
61/// for sol in &solutions {
62///     assert!(problem.is_valid_cover(sol));
63/// }
64/// ```
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct BicliqueCover {
67    /// The bipartite graph.
68    graph: BipartiteGraph,
69    /// Number of bicliques to use.
70    k: usize,
71}
72
73impl BicliqueCover {
74    /// Create a new Biclique Cover problem.
75    ///
76    /// # Arguments
77    /// * `graph` - The bipartite graph
78    /// * `k` - Number of bicliques
79    pub fn new(graph: BipartiteGraph, k: usize) -> Self {
80        Self { graph, k }
81    }
82
83    /// Create from a bipartite adjacency matrix.
84    ///
85    /// `Matrix[i][j] = 1` means edge between left vertex i and right vertex j.
86    pub fn from_matrix(matrix: &[Vec<u8>], k: usize) -> Self {
87        let left_size = matrix.len();
88        let right_size = if left_size > 0 { matrix[0].len() } else { 0 };
89
90        let mut edges = Vec::new();
91        for (i, row) in matrix.iter().enumerate() {
92            for (j, &val) in row.iter().enumerate() {
93                if val != 0 {
94                    edges.push((i, j));
95                }
96            }
97        }
98
99        Self {
100            graph: BipartiteGraph::new(left_size, right_size, edges),
101            k,
102        }
103    }
104
105    /// Get the bipartite graph.
106    pub fn graph(&self) -> &BipartiteGraph {
107        &self.graph
108    }
109
110    /// Get the left partition size.
111    pub fn left_size(&self) -> usize {
112        self.graph.left_size()
113    }
114
115    /// Get the right partition size.
116    pub fn right_size(&self) -> usize {
117        self.graph.right_size()
118    }
119
120    /// Get the number of vertices.
121    pub fn num_vertices(&self) -> usize {
122        self.graph.left_size() + self.graph.right_size()
123    }
124
125    /// Get the number of edges.
126    pub fn num_edges(&self) -> usize {
127        self.graph.left_edges().len()
128    }
129
130    /// Get k (number of bicliques).
131    pub fn k(&self) -> usize {
132        self.k
133    }
134
135    /// Get the rank (alias for `k()`).
136    pub fn rank(&self) -> usize {
137        self.k()
138    }
139
140    /// Convert a configuration to biclique memberships.
141    ///
142    /// Config is a flat array where each vertex has k binary variables
143    /// indicating membership in each of the k bicliques.
144    /// Returns: (left_memberships, right_memberships) where each is a Vec of k HashSets.
145    fn get_biclique_memberships(
146        &self,
147        config: &[usize],
148    ) -> (Vec<HashSet<usize>>, Vec<HashSet<usize>>) {
149        let n = self.num_vertices();
150        let left_size = self.graph.left_size();
151        let mut left_bicliques: Vec<HashSet<usize>> = vec![HashSet::new(); self.k];
152        let mut right_bicliques: Vec<HashSet<usize>> = vec![HashSet::new(); self.k];
153
154        for v in 0..n {
155            for b in 0..self.k {
156                let idx = v * self.k + b;
157                if config.get(idx).copied().unwrap_or(0) == 1 {
158                    if v < left_size {
159                        left_bicliques[b].insert(v);
160                    } else {
161                        right_bicliques[b].insert(v);
162                    }
163                }
164            }
165        }
166
167        (left_bicliques, right_bicliques)
168    }
169
170    /// Check if an edge is covered by the bicliques.
171    ///
172    /// Takes edge endpoints in unified vertex space.
173    fn is_edge_covered(&self, left: usize, right: usize, config: &[usize]) -> bool {
174        let (left_bicliques, right_bicliques) = self.get_biclique_memberships(config);
175
176        // Edge is covered if both endpoints are in the same biclique
177        for b in 0..self.k {
178            if left_bicliques[b].contains(&left) && right_bicliques[b].contains(&right) {
179                return true;
180            }
181        }
182        false
183    }
184
185    /// Check if a configuration is a valid biclique cover.
186    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
187        self.is_valid_cover(config)
188    }
189
190    /// Check if the configuration is a valid biclique cover.
191    ///
192    /// Under the classical definition, a biclique is a complete bipartite
193    /// subgraph of the input graph: every pair `(l, r)` with `l ∈ L_b`
194    /// and `r ∈ R_b` must be an edge of `G`. A configuration is a valid
195    /// biclique cover iff every biclique is a sub-biclique of `G` and
196    /// every edge of `G` is covered by at least one biclique.
197    pub fn is_valid_cover(&self, config: &[usize]) -> bool {
198        use crate::topology::Graph;
199        let (left_bicliques, right_bicliques) = self.get_biclique_memberships(config);
200        let left_size = self.graph.left_size();
201        // Every biclique must be a sub-biclique of G (no non-edges covered).
202        for b in 0..self.k {
203            for &l in &left_bicliques[b] {
204                for &r in &right_bicliques[b] {
205                    // Endpoints come from get_biclique_memberships in unified
206                    // vertex space: l < left_size, r >= left_size.
207                    debug_assert!(l < left_size && r >= left_size);
208                    if !self.graph.has_edge(l, r) {
209                        return false;
210                    }
211                }
212            }
213        }
214        // Every edge of G must be covered by at least one biclique.
215        self.graph
216            .edges()
217            .iter()
218            .all(|&(l, r)| self.is_edge_covered(l, r, config))
219    }
220
221    /// Count covered edges.
222    pub fn count_covered_edges(&self, config: &[usize]) -> usize {
223        use crate::topology::Graph;
224        self.graph
225            .edges()
226            .iter()
227            .filter(|&&(l, r)| self.is_edge_covered(l, r, config))
228            .count()
229    }
230
231    /// Count total biclique size (sum of vertices in all bicliques).
232    pub fn total_biclique_size(&self, config: &[usize]) -> usize {
233        config.iter().filter(|&&x| x == 1).count()
234    }
235}
236
237/// Check if a biclique configuration covers all edges.
238#[cfg(test)]
239pub(crate) fn is_biclique_cover(
240    edges: &[(usize, usize)],
241    left_bicliques: &[HashSet<usize>],
242    right_bicliques: &[HashSet<usize>],
243) -> bool {
244    let edge_set: HashSet<(usize, usize)> = edges
245        .iter()
246        .map(|&(u, v)| if u <= v { (u, v) } else { (v, u) })
247        .collect();
248
249    let all_bicliques_are_subgraphs =
250        left_bicliques
251            .iter()
252            .zip(right_bicliques.iter())
253            .all(|(lb, rb)| {
254                lb.iter().all(|&l| {
255                    rb.iter().all(|&r| {
256                        let edge = if l <= r { (l, r) } else { (r, l) };
257                        edge_set.contains(&edge)
258                    })
259                })
260            });
261
262    all_bicliques_are_subgraphs
263        && edges.iter().all(|&(l, r)| {
264            left_bicliques
265                .iter()
266                .zip(right_bicliques.iter())
267                .any(|(lb, rb)| lb.contains(&l) && rb.contains(&r))
268        })
269}
270
271impl Problem for BicliqueCover {
272    const NAME: &'static str = "BicliqueCover";
273    type Value = Min<i32>;
274
275    fn dims(&self) -> Vec<usize> {
276        // Each vertex has k binary variables (one per biclique)
277        vec![2; self.num_vertices() * self.k]
278    }
279
280    fn evaluate(&self, config: &[usize]) -> Min<i32> {
281        if !self.is_valid_cover(config) {
282            return Min(None);
283        }
284        Min(Some(self.total_biclique_size(config) as i32))
285    }
286
287    fn variant() -> Vec<(&'static str, &'static str)> {
288        crate::variant_params![]
289    }
290}
291
292crate::declare_variants! {
293    default BicliqueCover => "2^(num_vertices * rank)",
294}
295
296#[cfg(feature = "example-db")]
297pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
298    use crate::topology::BipartiteGraph;
299    // Biclique 0: L_0={ℓ_1}, R_0={r_1, r_2} — covers edges (0,0), (0,1).
300    // Biclique 1: L_1={ℓ_2}, R_1={r_2, r_3} — covers edges (1,1), (1,2).
301    // Both are sub-bicliques of G; every edge covered; total size = 6.
302    // Vertex-major layout with k=2: v=0 in b=0, v=1 in b=1, v=2 in b=0,
303    // v=3 in both, v=4 in b=1.
304    vec![crate::example_db::specs::ModelExampleSpec {
305        id: "biclique_cover",
306        instance: Box::new(BicliqueCover::new(
307            BipartiteGraph::new(2, 3, vec![(0, 0), (0, 1), (1, 1), (1, 2)]),
308            2,
309        )),
310        optimal_config: vec![1, 0, 0, 1, 1, 0, 1, 1, 0, 1],
311        optimal_value: serde_json::json!(6),
312    }]
313}
314
315#[cfg(test)]
316#[path = "../../unit_tests/models/graph/biclique_cover.rs"]
317mod tests;