problemreductions/models/graph/
biclique_cover.rs1use crate::registry::{CreateSpec, 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 category: crate::registry::ProblemCategory::Graph,
30 module_path: module_path!(),
31 description: "Cover bipartite edges with k bicliques",
32 fields: BicliqueCoverCreateSpec::FIELDS,
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct BicliqueCover {
63 graph: BipartiteGraph,
65 k: usize,
67}
68
69#[derive(Debug, Deserialize, crate::CreateSpec)]
70struct BicliqueCoverCreateSpec {
71 left: usize,
73 right: usize,
75 #[create(codec = "bipartite-edge-list")]
77 biedges: Vec<(usize, usize)>,
78 k: usize,
80}
81
82impl TryFrom<BicliqueCoverCreateSpec> for BicliqueCover {
83 type Error = crate::registry::ConstructionError;
84
85 fn try_from(spec: BicliqueCoverCreateSpec) -> Result<Self, Self::Error> {
86 for (edge_index, &(left_vertex, right_vertex)) in spec.biedges.iter().enumerate() {
87 if left_vertex >= spec.left {
88 return Err(format!(
89 "biedges[{edge_index}] left vertex {left_vertex} is out of bounds for left partition size {}",
90 spec.left
91 ).into());
92 }
93 if right_vertex >= spec.right {
94 return Err(format!(
95 "biedges[{edge_index}] right vertex {right_vertex} is out of bounds for right partition size {}",
96 spec.right
97 ).into());
98 }
99 }
100
101 let graph = BipartiteGraph::new(spec.left, spec.right, spec.biedges);
102 Ok(Self::new(graph, spec.k))
103 }
104}
105
106impl BicliqueCover {
107 pub fn new(graph: BipartiteGraph, k: usize) -> Self {
113 Self { graph, k }
114 }
115
116 pub fn from_matrix(matrix: &[Vec<u8>], k: usize) -> Self {
120 let left_size = matrix.len();
121 let right_size = if left_size > 0 { matrix[0].len() } else { 0 };
122
123 let mut edges = Vec::new();
124 for (i, row) in matrix.iter().enumerate() {
125 for (j, &val) in row.iter().enumerate() {
126 if val != 0 {
127 edges.push((i, j));
128 }
129 }
130 }
131
132 Self {
133 graph: BipartiteGraph::new(left_size, right_size, edges),
134 k,
135 }
136 }
137
138 pub fn graph(&self) -> &BipartiteGraph {
140 &self.graph
141 }
142
143 pub fn left_size(&self) -> usize {
145 self.graph.left_size()
146 }
147
148 pub fn right_size(&self) -> usize {
150 self.graph.right_size()
151 }
152
153 pub fn num_vertices(&self) -> usize {
155 self.graph.left_size() + self.graph.right_size()
156 }
157
158 pub fn num_edges(&self) -> usize {
160 self.graph.left_edges().len()
161 }
162
163 pub fn k(&self) -> usize {
165 self.k
166 }
167
168 pub fn rank(&self) -> usize {
170 self.k()
171 }
172
173 fn get_biclique_memberships(
178 &self,
179 config: &[Vec<bool>],
180 ) -> (Vec<HashSet<usize>>, Vec<HashSet<usize>>) {
181 let n = self.num_vertices();
182 let left_size = self.graph.left_size();
183 let mut left_bicliques: Vec<HashSet<usize>> = vec![HashSet::new(); self.k];
184 let mut right_bicliques: Vec<HashSet<usize>> = vec![HashSet::new(); self.k];
185
186 for v in 0..n {
187 for b in 0..self.k {
188 if config
189 .get(b)
190 .and_then(|memberships| memberships.get(v))
191 .copied()
192 .unwrap_or(false)
193 {
194 if v < left_size {
195 left_bicliques[b].insert(v);
196 } else {
197 right_bicliques[b].insert(v);
198 }
199 }
200 }
201 }
202
203 (left_bicliques, right_bicliques)
204 }
205
206 fn is_edge_covered(&self, left: usize, right: usize, config: &[Vec<bool>]) -> bool {
210 let (left_bicliques, right_bicliques) = self.get_biclique_memberships(config);
211
212 for b in 0..self.k {
214 if left_bicliques[b].contains(&left) && right_bicliques[b].contains(&right) {
215 return true;
216 }
217 }
218 false
219 }
220
221 pub fn is_valid_solution(&self, config: &[Vec<bool>]) -> bool {
223 self.is_valid_cover(config)
224 }
225
226 pub fn is_valid_cover(&self, config: &[Vec<bool>]) -> bool {
234 use crate::topology::Graph;
235 let (left_bicliques, right_bicliques) = self.get_biclique_memberships(config);
236 let left_size = self.graph.left_size();
237 for b in 0..self.k {
239 for &l in &left_bicliques[b] {
240 for &r in &right_bicliques[b] {
241 debug_assert!(l < left_size && r >= left_size);
244 if !self.graph.has_edge(l, r) {
245 return false;
246 }
247 }
248 }
249 }
250 self.graph
252 .edges()
253 .iter()
254 .all(|&(l, r)| self.is_edge_covered(l, r, config))
255 }
256
257 pub fn count_covered_edges(
259 &self,
260 config: &[Vec<bool>],
261 ) -> Result<i64, crate::traits::EvaluationError> {
262 use crate::topology::Graph;
263 let count = self
264 .graph
265 .edges()
266 .iter()
267 .filter(|&&(l, r)| self.is_edge_covered(l, r, config))
268 .count();
269 i64::try_from(count).map_err(|_| {
270 crate::traits::EvaluationError::IntegerOverflow(
271 "converting covered-edge count to i64".into(),
272 )
273 })
274 }
275
276 pub fn total_biclique_size(
278 &self,
279 config: &[Vec<bool>],
280 ) -> Result<i64, crate::traits::EvaluationError> {
281 let size = config
282 .iter()
283 .flatten()
284 .filter(|&&selected| selected)
285 .count();
286 i64::try_from(size).map_err(|_| {
287 crate::traits::EvaluationError::IntegerOverflow(
288 "converting total biclique size to i64".into(),
289 )
290 })
291 }
292}
293
294#[cfg(test)]
296pub(crate) fn is_biclique_cover(
297 edges: &[(usize, usize)],
298 left_bicliques: &[HashSet<usize>],
299 right_bicliques: &[HashSet<usize>],
300) -> bool {
301 let edge_set: HashSet<(usize, usize)> = edges
302 .iter()
303 .map(|&(u, v)| if u <= v { (u, v) } else { (v, u) })
304 .collect();
305
306 let all_bicliques_are_subgraphs =
307 left_bicliques
308 .iter()
309 .zip(right_bicliques.iter())
310 .all(|(lb, rb)| {
311 lb.iter().all(|&l| {
312 rb.iter().all(|&r| {
313 let edge = if l <= r { (l, r) } else { (r, l) };
314 edge_set.contains(&edge)
315 })
316 })
317 });
318
319 all_bicliques_are_subgraphs
320 && edges.iter().all(|&(l, r)| {
321 left_bicliques
322 .iter()
323 .zip(right_bicliques.iter())
324 .any(|(lb, rb)| lb.contains(&l) && rb.contains(&r))
325 })
326}
327
328impl Problem for BicliqueCover {
329 const NAME: &'static str = "BicliqueCover";
330 type Solution = Vec<Vec<bool>>;
331 type Value = Min<i64>;
332
333 crate::problem_parameters![
334 ("left_size", left_size),
335 ("num_edges", num_edges),
336 ("num_vertices", num_vertices),
337 ("rank", rank),
338 ("right_size", right_size),
339 ];
340
341 fn evaluate(
342 &self,
343 solution: &Self::Solution,
344 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
345 if solution.len() != self.k
346 || solution
347 .iter()
348 .any(|biclique| biclique.len() != self.num_vertices())
349 {
350 return Err(crate::traits::EvaluationError::InvalidConfiguration(
351 "biclique membership dimensions do not match the instance".into(),
352 ));
353 }
354 Ok({
355 if !self.is_valid_cover(solution) {
356 return Ok(Min(None));
357 }
358 Min(Some(self.total_biclique_size(solution)?))
359 })
360 }
361
362 fn variant() -> Vec<(&'static str, &'static str)> {
363 crate::variant_params![]
364 }
365}
366
367impl crate::solvers::BruteForceProblem for BicliqueCover {
368 fn dimensions(&self) -> Vec<usize> {
369 vec![2; self.num_vertices() * self.k]
371 }
372}
373
374crate::declare_variants! {
375 default BicliqueCover => "2^(num_vertices * rank)" create BicliqueCoverCreateSpec,
376}
377
378crate::register_brute_force! {
379 BicliqueCover decode |problem: &BicliqueCover, indices: Vec<usize>| (0..problem.rank()).map(|biclique| (0..problem.num_vertices()).map(|vertex| indices[vertex * problem.rank() + biclique] != 0).collect()).collect(),
380}
381
382#[cfg(feature = "example-db")]
383pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
384 use crate::topology::BipartiteGraph;
385 vec![crate::example_db::specs::ModelExampleSpec {
390 id: "biclique_cover",
391 instance: Box::new(BicliqueCover::new(
392 BipartiteGraph::new(2, 3, vec![(0, 0), (0, 1), (1, 1), (1, 2)]),
393 2,
394 )),
395 optimal_config: serde_json::json!(vec![
396 vec![true, false, true, true, false],
397 vec![false, true, false, true, true]
398 ]),
399 optimal_value: serde_json::json!(6),
400 }]
401}
402
403#[cfg(test)]
404#[path = "../../unit_tests/models/graph/biclique_cover.rs"]
405mod tests;