problemreductions/models/graph/
biclique_cover.rs

1//! Biclique Cover problem implementation.
2//!
3//! The Biclique Cover problem asks for the minimum number of bicliques
4//! (complete bipartite subgraphs) needed to cover all edges of a bipartite graph.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::topology::BipartiteGraph;
8use crate::traits::{OptimizationProblem, Problem};
9use crate::types::{Direction, SolutionSize};
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "BicliqueCover",
16        module_path: module_path!(),
17        description: "Cover bipartite edges with k bicliques",
18        fields: &[
19            FieldInfo { name: "left_size", type_name: "usize", description: "Vertices in left partition" },
20            FieldInfo { name: "right_size", type_name: "usize", description: "Vertices in right partition" },
21            FieldInfo { name: "edges", type_name: "Vec<(usize, usize)>", description: "Bipartite edges" },
22            FieldInfo { name: "k", type_name: "usize", description: "Number of bicliques" },
23        ],
24    }
25}
26
27/// The Biclique Cover problem.
28///
29/// Given a bipartite graph with vertex sets L and R, find k bicliques
30/// that together cover all edges. Each vertex can be in any subset of the k bicliques.
31///
32/// # Example
33///
34/// ```
35/// use problemreductions::models::graph::BicliqueCover;
36/// use problemreductions::topology::BipartiteGraph;
37/// use problemreductions::{Problem, Solver, BruteForce};
38///
39/// // Bipartite graph: L = {0, 1}, R = {0, 1}
40/// // Edges: (0,0), (0,1), (1,0) in bipartite-local coordinates
41/// let graph = BipartiteGraph::new(2, 2, vec![(0, 0), (0, 1), (1, 0)]);
42/// let problem = BicliqueCover::new(graph, 2);
43///
44/// let solver = BruteForce::new();
45/// let solutions = solver.find_all_best(&problem);
46///
47/// // Check coverage
48/// for sol in &solutions {
49///     assert!(problem.is_valid_cover(sol));
50/// }
51/// ```
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct BicliqueCover {
54    /// The bipartite graph.
55    graph: BipartiteGraph,
56    /// Number of bicliques to use.
57    k: usize,
58}
59
60impl BicliqueCover {
61    /// Create a new Biclique Cover problem.
62    ///
63    /// # Arguments
64    /// * `graph` - The bipartite graph
65    /// * `k` - Number of bicliques
66    pub fn new(graph: BipartiteGraph, k: usize) -> Self {
67        Self { graph, k }
68    }
69
70    /// Create from a bipartite adjacency matrix.
71    ///
72    /// `Matrix[i][j] = 1` means edge between left vertex i and right vertex j.
73    pub fn from_matrix(matrix: &[Vec<u8>], k: usize) -> Self {
74        let left_size = matrix.len();
75        let right_size = if left_size > 0 { matrix[0].len() } else { 0 };
76
77        let mut edges = Vec::new();
78        for (i, row) in matrix.iter().enumerate() {
79            for (j, &val) in row.iter().enumerate() {
80                if val != 0 {
81                    edges.push((i, j));
82                }
83            }
84        }
85
86        Self {
87            graph: BipartiteGraph::new(left_size, right_size, edges),
88            k,
89        }
90    }
91
92    /// Get the bipartite graph.
93    pub fn graph(&self) -> &BipartiteGraph {
94        &self.graph
95    }
96
97    /// Get the left partition size.
98    pub fn left_size(&self) -> usize {
99        self.graph.left_size()
100    }
101
102    /// Get the right partition size.
103    pub fn right_size(&self) -> usize {
104        self.graph.right_size()
105    }
106
107    /// Get the number of vertices.
108    pub fn num_vertices(&self) -> usize {
109        self.graph.left_size() + self.graph.right_size()
110    }
111
112    /// Get the number of edges.
113    pub fn num_edges(&self) -> usize {
114        self.graph.left_edges().len()
115    }
116
117    /// Get k (number of bicliques).
118    pub fn k(&self) -> usize {
119        self.k
120    }
121
122    /// Get the rank (alias for `k()`).
123    pub fn rank(&self) -> usize {
124        self.k()
125    }
126
127    /// Convert a configuration to biclique memberships.
128    ///
129    /// Config is a flat array where each vertex has k binary variables
130    /// indicating membership in each of the k bicliques.
131    /// Returns: (left_memberships, right_memberships) where each is a Vec of k HashSets.
132    fn get_biclique_memberships(
133        &self,
134        config: &[usize],
135    ) -> (Vec<HashSet<usize>>, Vec<HashSet<usize>>) {
136        let n = self.num_vertices();
137        let left_size = self.graph.left_size();
138        let mut left_bicliques: Vec<HashSet<usize>> = vec![HashSet::new(); self.k];
139        let mut right_bicliques: Vec<HashSet<usize>> = vec![HashSet::new(); self.k];
140
141        for v in 0..n {
142            for b in 0..self.k {
143                let idx = v * self.k + b;
144                if config.get(idx).copied().unwrap_or(0) == 1 {
145                    if v < left_size {
146                        left_bicliques[b].insert(v);
147                    } else {
148                        right_bicliques[b].insert(v);
149                    }
150                }
151            }
152        }
153
154        (left_bicliques, right_bicliques)
155    }
156
157    /// Check if an edge is covered by the bicliques.
158    ///
159    /// Takes edge endpoints in unified vertex space.
160    fn is_edge_covered(&self, left: usize, right: usize, config: &[usize]) -> bool {
161        let (left_bicliques, right_bicliques) = self.get_biclique_memberships(config);
162
163        // Edge is covered if both endpoints are in the same biclique
164        for b in 0..self.k {
165            if left_bicliques[b].contains(&left) && right_bicliques[b].contains(&right) {
166                return true;
167            }
168        }
169        false
170    }
171
172    /// Check if a configuration is a valid biclique cover.
173    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
174        self.is_valid_cover(config)
175    }
176
177    /// Check if all edges are covered.
178    pub fn is_valid_cover(&self, config: &[usize]) -> bool {
179        use crate::topology::Graph;
180        self.graph
181            .edges()
182            .iter()
183            .all(|&(l, r)| self.is_edge_covered(l, r, config))
184    }
185
186    /// Count covered edges.
187    pub fn count_covered_edges(&self, config: &[usize]) -> usize {
188        use crate::topology::Graph;
189        self.graph
190            .edges()
191            .iter()
192            .filter(|&&(l, r)| self.is_edge_covered(l, r, config))
193            .count()
194    }
195
196    /// Count total biclique size (sum of vertices in all bicliques).
197    pub fn total_biclique_size(&self, config: &[usize]) -> usize {
198        config.iter().filter(|&&x| x == 1).count()
199    }
200}
201
202/// Check if a biclique configuration covers all edges.
203#[cfg(test)]
204pub(crate) fn is_biclique_cover(
205    edges: &[(usize, usize)],
206    left_bicliques: &[HashSet<usize>],
207    right_bicliques: &[HashSet<usize>],
208) -> bool {
209    edges.iter().all(|&(l, r)| {
210        left_bicliques
211            .iter()
212            .zip(right_bicliques.iter())
213            .any(|(lb, rb)| lb.contains(&l) && rb.contains(&r))
214    })
215}
216
217impl Problem for BicliqueCover {
218    const NAME: &'static str = "BicliqueCover";
219    type Metric = SolutionSize<i32>;
220
221    fn dims(&self) -> Vec<usize> {
222        // Each vertex has k binary variables (one per biclique)
223        vec![2; self.num_vertices() * self.k]
224    }
225
226    fn evaluate(&self, config: &[usize]) -> SolutionSize<i32> {
227        if !self.is_valid_cover(config) {
228            return SolutionSize::Invalid;
229        }
230        SolutionSize::Valid(self.total_biclique_size(config) as i32)
231    }
232
233    fn variant() -> Vec<(&'static str, &'static str)> {
234        crate::variant_params![]
235    }
236}
237
238impl OptimizationProblem for BicliqueCover {
239    type Value = i32;
240
241    fn direction(&self) -> Direction {
242        Direction::Minimize
243    }
244}
245
246crate::declare_variants! {
247    BicliqueCover => "2^num_vertices",
248}
249
250#[cfg(test)]
251#[path = "../../unit_tests/models/graph/biclique_cover.rs"]
252mod tests;