Skip to content

Commit 06b2eba

Browse files
GiggleLiuclaude
andcommitted
Add BicliqueCover → BMF inverse; drop dominated BicliqueCover → ILP
With exact BMF and sub-biclique BicliqueCover now both in place, the two models are isomorphic up to a layout transpose. Add the inverse reduction so the graph is bidirectional, factor the shared transpose into helpers, and remove the direct BicliqueCover → ILP rule that is strictly dominated by the two-hop path through BMF. - `src/rules/bmf_bicliquecover.rs` now exposes `config_bc_to_bmf` and `config_bmf_to_bc`. The forward reduction's `extract_solution` calls the former; the new inverse reduction's `extract_solution` calls the latter. Two helpers, one per direction — same transpose logic, no duplication. - `src/rules/bicliquecover_bmf.rs` (new) reduces `BicliqueCover(G, k)` to `BMF(A_G, k)` by reading off the biadjacency matrix of `G`. Overhead: `rows = cols = num_vertices` (loose bound, m ≤ m+n and n ≤ m+n), `rank = rank`. - The direct `BicliqueCover → ILP` rule (`bicliquecover_ilp.rs` and its unit tests) is removed. Path search automatically resolves `pred path BicliqueCover ILP` to the dominating two-hop path `BicliqueCover → BMF → ILP`, which has the cleaner equality-constrained BMF → ILP formulation. - Paper: drop the `reduction-rule("BicliqueCover", "ILP")` entry; keep the new `reduction-rule("BicliqueCover", "BMF")` alongside the forward rule. - New unit tests cover the inverse reduction (structure, closed-loop on K_{2,2} at rank 1 and identity biadjacency at rank 2, infeasibility at rank 1) plus a roundtrip test pinning `config_bc_to_bmf ∘ config_bmf_to_bc = id`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3a37e70 commit 06b2eba

7 files changed

Lines changed: 220 additions & 251 deletions

File tree

docs/paper/reductions.typ

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14457,25 +14457,6 @@ The following reductions to Integer Linear Programming are straightforward formu
1445714457
_Solution extraction._ Output the concatenated left/right binary selection vector.
1445814458
]
1445914459

14460-
#reduction-rule("BicliqueCover", "ILP")[
14461-
Use $k$ candidate bicliques, assign vertices to any of them, force every graph edge to be covered by some common biclique, and minimize the total membership size.
14462-
][
14463-
_Construction._ Variables: binary $x_(l,b)$ for left vertices, binary $y_(r,b)$ for right vertices, and binary $z_((l,r),b)$ linearizing $x_(l,b) y_(r,b)$. The ILP is:
14464-
$
14465-
min quad & sum_(l,b) x_(l,b) + sum_(r,b) y_(r,b) \
14466-
"subject to" quad & z_((l,r),b) <= x_(l,b) quad forall l, r, b \
14467-
& z_((l,r),b) <= y_(r,b) quad forall l, r, b \
14468-
& z_((l,r),b) >= x_(l,b) + y_(r,b) - 1 quad forall l, r, b \
14469-
& sum_b z_((l,r),b) >= 1 quad forall (l, r) in E \
14470-
& x_(l,b) + y_(r,b) <= 1 quad forall (l, r) in.not E, b \
14471-
& x_(l,b), y_(r,b), z_((l,r),b) in {0, 1}.
14472-
$
14473-
14474-
_Correctness._ ($arrow.r.double$) Any valid $k$-biclique cover assigns each covered edge to a biclique containing both endpoints, with objective equal to the total biclique size. ($arrow.l.double$) Any feasible ILP solution defines $k$ complete bipartite subgraphs whose union covers every edge, and the objective is exactly the source objective.
14475-
14476-
_Solution extraction._ Output the flattened vertex-by-biclique membership bits and discard the coverage auxiliaries.
14477-
]
14478-
1447914460
#reduction-rule("BiconnectivityAugmentation", "ILP")[
1448014461
Select candidate edges under the budget and, for every deleted vertex, certify that the remaining augmented graph stays connected by a flow witness.
1448114462
][
@@ -14668,6 +14649,16 @@ The following reductions to Integer Linear Programming are straightforward formu
1466814649
_Solution extraction._ Given a BicliqueCover witness (vertex-major, $"cfg"_("BC")[v k + r] in {0, 1}$), set $B_(i,r) = "cfg"_("BC")[i k + r]$ and $C_(r,j) = "cfg"_("BC")[(m + j) k + r]$. The left half is a direct copy; the right half transposes from vertex-major to biclique-row-major.
1466914650
]
1467014651

14652+
#reduction-rule("BicliqueCover", "BMF")[
14653+
The inverse of the matrix-to-graph map: read off the biadjacency matrix $A_G in {0,1}^(|L| times |R|)$ of the bipartite graph $G$ and reuse the same rank $k$.
14654+
][
14655+
_Construction._ Given an instance $(G, k)$ of BicliqueCover, emit the BMF instance $(A_G, k)$ where $A_G[i][j] = 1$ iff $(i, j) in E(G)$. Source and target live in the same variable space, with the layout permutation described below.
14656+
14657+
_Correctness._ Symmetric to the forward rule: the same Monson–Pullman–Rees equivalence (sub-bicliques of $G$ $<->$ rank-1 factors of $A_G$) holds in both directions, and the two objectives — total vertex memberships and $|B|_1 + |C|_1$ — agree by construction.
14658+
14659+
_Solution extraction._ Inverse transpose of the forward map: given a BMF witness (B row-major followed by C row-major), set $"cfg"_("BC")[i k + r] = B_(i,r)$ for left vertices and $"cfg"_("BC")[(m + j) k + r] = C_(r,j)$ for right vertices.
14660+
]
14661+
1467114662
#reduction-rule("ConsecutiveBlockMinimization", "ILP")[
1467214663
Permute the columns with a one-hot assignment and count row-wise block starts by detecting each 0-to-1 transition after permutation.
1467314664
][

src/rules/bicliquecover_bmf.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//! Reduction from BicliqueCover to BMF (exact Boolean Matrix Factorization).
2+
//!
3+
//! Inverse of [`crate::rules::bmf_bicliquecover`]: a bipartite graph
4+
//! `G = (L, R, E)` is encoded by its `|L| × |R|` biadjacency matrix
5+
//! `A_G` with `A_G[i][j] = 1` iff `(i, j) ∈ E`. Under the classical
6+
//! sub-biclique semantics, a biclique cover of `G` by `k` sub-bicliques
7+
//! is exactly an exact rank-`k` Boolean factorization of `A_G`, and the
8+
//! total biclique size equals `|B|_1 + |C|_1`.
9+
//!
10+
//! Layout mapping is the inverse transpose from the forward reduction —
11+
//! the shared helpers [`super::bmf_bicliquecover::config_bmf_to_bc`] and
12+
//! `config_bc_to_bmf` live in the sibling module.
13+
14+
use crate::models::algebraic::BMF;
15+
use crate::models::graph::BicliqueCover;
16+
use crate::reduction;
17+
use crate::rules::bmf_bicliquecover::config_bmf_to_bc;
18+
use crate::rules::traits::{ReduceTo, ReductionResult};
19+
20+
/// Result of reducing BicliqueCover to BMF.
21+
#[derive(Debug, Clone)]
22+
pub struct ReductionBicliqueCoverToBMF {
23+
target: BMF,
24+
m: usize,
25+
n: usize,
26+
k: usize,
27+
}
28+
29+
impl ReductionResult for ReductionBicliqueCoverToBMF {
30+
type Source = BicliqueCover;
31+
type Target = BMF;
32+
33+
fn target_problem(&self) -> &BMF {
34+
&self.target
35+
}
36+
37+
/// Map a BMF config (B row-major, C row-major) to a BicliqueCover
38+
/// config (vertex-major) via the inverse transpose.
39+
fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
40+
config_bmf_to_bc(target_solution, self.m, self.n, self.k)
41+
}
42+
}
43+
44+
#[reduction(
45+
overhead = {
46+
rows = "num_vertices",
47+
cols = "num_vertices",
48+
rank = "rank",
49+
}
50+
)]
51+
impl ReduceTo<BMF> for BicliqueCover {
52+
type Result = ReductionBicliqueCoverToBMF;
53+
54+
fn reduce_to(&self) -> Self::Result {
55+
let m = self.left_size();
56+
let n = self.right_size();
57+
let k = self.k();
58+
let mut matrix = vec![vec![false; n]; m];
59+
for &(i, j) in self.graph().left_edges() {
60+
matrix[i][j] = true;
61+
}
62+
let target = BMF::new(matrix, k);
63+
ReductionBicliqueCoverToBMF { target, m, n, k }
64+
}
65+
}
66+
67+
#[cfg(feature = "example-db")]
68+
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
69+
use crate::export::SolutionPair;
70+
use crate::topology::BipartiteGraph;
71+
72+
vec![crate::example_db::specs::RuleExampleSpec {
73+
id: "bicliquecover_to_bmf",
74+
build: || {
75+
// Single K_{2,2} biclique at rank 1 — matches the forward example.
76+
let source = BicliqueCover::new(
77+
BipartiteGraph::new(2, 2, vec![(0, 0), (0, 1), (1, 0), (1, 1)]),
78+
1,
79+
);
80+
crate::example_db::specs::rule_example_with_witness::<_, BMF>(
81+
source,
82+
SolutionPair {
83+
// BicliqueCover (vertex-major, k=1): all 4 vertices in biclique 0
84+
source_config: vec![1, 1, 1, 1],
85+
// BMF (B row-major then C row-major): B=[[1],[1]], C=[[1,1]]
86+
target_config: vec![1, 1, 1, 1],
87+
},
88+
)
89+
},
90+
}]
91+
}
92+
93+
#[cfg(test)]
94+
#[path = "../unit_tests/rules/bicliquecover_bmf.rs"]
95+
mod tests;

src/rules/bicliquecover_ilp.rs

Lines changed: 0 additions & 139 deletions
This file was deleted.

src/rules/bmf_bicliquecover.rs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,43 @@ use crate::reduction;
2020
use crate::rules::traits::{ReduceTo, ReductionResult};
2121
use crate::topology::BipartiteGraph;
2222

23+
/// Convert a BicliqueCover config (vertex-major: index `v*k + b`) to a BMF
24+
/// config (B row-major at `[0, m*k)` then C row-major at `[m*k, m*k + k*n)`).
25+
///
26+
/// The left half copies unchanged; the right half transposes from
27+
/// vertex-major `(m+j)*k + l` to biclique-row-major `m*k + l*n + j`.
28+
pub(crate) fn config_bc_to_bmf(bc: &[usize], m: usize, n: usize, k: usize) -> Vec<usize> {
29+
let mut bmf = vec![0usize; m * k + k * n];
30+
for i in 0..m {
31+
for l in 0..k {
32+
bmf[i * k + l] = bc[i * k + l];
33+
}
34+
}
35+
for l in 0..k {
36+
for j in 0..n {
37+
bmf[m * k + l * n + j] = bc[(m + j) * k + l];
38+
}
39+
}
40+
bmf
41+
}
42+
43+
/// Inverse of [`config_bc_to_bmf`]: BMF config (B row-major then C row-major)
44+
/// to BicliqueCover config (vertex-major).
45+
pub(crate) fn config_bmf_to_bc(bmf: &[usize], m: usize, n: usize, k: usize) -> Vec<usize> {
46+
let mut bc = vec![0usize; (m + n) * k];
47+
for i in 0..m {
48+
for l in 0..k {
49+
bc[i * k + l] = bmf[i * k + l];
50+
}
51+
}
52+
for l in 0..k {
53+
for j in 0..n {
54+
bc[(m + j) * k + l] = bmf[m * k + l * n + j];
55+
}
56+
}
57+
bc
58+
}
59+
2360
/// Result of reducing BMF to BicliqueCover.
2461
#[derive(Debug, Clone)]
2562
pub struct ReductionBMFToBicliqueCover {
@@ -39,26 +76,7 @@ impl ReductionResult for ReductionBMFToBicliqueCover {
3976

4077
/// Map a BicliqueCover config (vertex-major) back to a BMF config (B row-major, then C row-major).
4178
fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
42-
let m = self.m;
43-
let n = self.n;
44-
let k = self.k;
45-
let mut source = vec![0usize; m * k + k * n];
46-
// Left half: identical layout — BicliqueCover left vertex i, biclique l at index i*k + l
47-
// matches BMF B[i][l] at index i*k + l.
48-
for i in 0..m {
49-
for l in 0..k {
50-
source[i * k + l] = target_solution[i * k + l];
51-
}
52-
}
53-
// Right half: transpose from vertex-major to biclique-row-major.
54-
// BicliqueCover right vertex j, biclique l at index (m + j)*k + l.
55-
// BMF C[l][j] at index m*k + l*n + j.
56-
for l in 0..k {
57-
for j in 0..n {
58-
source[m * k + l * n + j] = target_solution[(m + j) * k + l];
59-
}
60-
}
61-
source
79+
config_bc_to_bmf(target_solution, self.m, self.n, self.k)
6280
}
6381
}
6482

src/rules/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub use cost::{
88
};
99
pub use registry::{EdgeCapabilities, ReductionEntry, ReductionOverhead};
1010

11+
pub(crate) mod bicliquecover_bmf;
1112
pub(crate) mod bmf_bicliquecover;
1213
pub(crate) mod circuit_sat;
1314
pub(crate) mod circuit_spinglass;
@@ -141,8 +142,6 @@ pub(crate) mod acyclicpartition_ilp;
141142
#[cfg(feature = "ilp-solver")]
142143
pub(crate) mod balancedcompletebipartitesubgraph_ilp;
143144
#[cfg(feature = "ilp-solver")]
144-
pub(crate) mod bicliquecover_ilp;
145-
#[cfg(feature = "ilp-solver")]
146145
pub(crate) mod biconnectivityaugmentation_ilp;
147146
#[cfg(feature = "ilp-solver")]
148147
pub(crate) mod binpacking_ilp;
@@ -380,6 +379,7 @@ pub use traits::{
380379
#[cfg(feature = "example-db")]
381380
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
382381
let mut specs = Vec::new();
382+
specs.extend(bicliquecover_bmf::canonical_rule_example_specs());
383383
specs.extend(bmf_bicliquecover::canonical_rule_example_specs());
384384
specs.extend(circuit_sat::canonical_rule_example_specs());
385385
specs.extend(circuit_spinglass::canonical_rule_example_specs());
@@ -509,7 +509,6 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
509509
{
510510
specs.extend(acyclicpartition_ilp::canonical_rule_example_specs());
511511
specs.extend(balancedcompletebipartitesubgraph_ilp::canonical_rule_example_specs());
512-
specs.extend(bicliquecover_ilp::canonical_rule_example_specs());
513512
specs.extend(biconnectivityaugmentation_ilp::canonical_rule_example_specs());
514513
specs.extend(binpacking_ilp::canonical_rule_example_specs());
515514
specs.extend(bmf_ilp::canonical_rule_example_specs());

0 commit comments

Comments
 (0)