Skip to content

Commit 9ff62f0

Browse files
GiggleLiuclaude
andauthored
Redefine BMF as exact factorization; fix BicliqueCover semantics; add BMF → BicliqueCover (#1056)
* Redefine BMF as exact Boolean matrix factorization The Rust BMF model previously minimized Hamming distance between A and B*C (approximate factorization). This redefines it to the classical exact form: feasible iff B*C = A, infeasible otherwise, with objective = total number of 1s in B and C (Monson-Pullman-Rees 1995). Model changes (src/models/algebraic/bmf.rs): - evaluate() returns Min(None) when B*C != A, else Min(Some(|B|_1 + |C|_1)) - Add total_factor_size() helper - Fix canonical example: the previous optimal_config was not actually exact (verified by hand: Hamming distance 2, not 0). New config is a true exact factorization with total factor size 8. BMF -> ILP reduction (src/rules/bmf_ilp.rs): - Drop error variables e_{i,j} and their |A - w| inequalities - Pin w_{i,j} = A_{i,j} (exact reconstruction) - Objective changes from sum e_{i,j} to sum b_{i,r} + sum c_{r,j} - Overhead shrinks: num_vars drops by m*n, num_constraints drops by m*n BMF -> BicliqueCover reduction intentionally NOT added: the codebase's BicliqueCover is the OR-cover variant (allows bicliques to cover non-edges), which is strictly weaker than exact BMF. A closed-loop reduction would extract invalid (non-exact) BMF configs. The classical equivalence with biclique cover requires the stronger "sub-biclique-of-G" definition that BicliqueCover does not currently enforce. Julia parity fixture for BMF is removed (tests/data/jl/bmf.json) — the old file encoded Hamming-distance semantics that no longer apply. Related generator code in scripts/jl/generate_testdata.jl is commented out with a note explaining the semantics change. Paper (docs/paper/reductions.typ) is updated: BMF problem definition now states exact factorization and |B|_1 + |C|_1 objective; BMF -> ILP rule replaces the error-variable construction with the equality-constrained formulation. Mentions the Boolean rank / biclique cover number equivalence in the Extra Remark section. Two incidental cargo fmt cleanups are included in partition_into_paths_of_length_2.rs, maximumindependentset_triangular.rs, and reduction_graph.rs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Enforce classical sub-biclique semantics and add BMF → BicliqueCover BicliqueCover's `is_valid_cover` previously checked only that every graph edge is covered by some biclique — it did not forbid a biclique from containing a non-edge of G. That's the OR-cover / cover-by-rectangles variant, which disagrees with the docstring (and with the textbook definition). It's also why the earlier attempt at BMF → BicliqueCover was unsound: a 2×2 identity at rank 1 was infeasible for exact BMF but "feasible" for OR-cover via the K_{2,2} pseudo-biclique. Changes: - `BicliqueCover::is_valid_cover` now also requires every biclique's `L_b × R_b` to be a subset of E(G). This aligns implementation with the docstring and with the paper's constraint `x_{l,b} + y_{r,b} ≤ 1 for non-edges` (which the paper already described but the code didn't enforce). - `BicliqueCover → ILP` adds the `x + y ≤ 1` constraint for every non-edge and every biclique, matching the paper. - `test_biclique_problem` updated: `[1,1,1,1]` on a graph with only edge (0,0) now evaluates to `Min(None)` (it's a pseudo-biclique covering non-edges). - Canonical BicliqueCover example switched from the OR-cover optimum (total size 5) to the correct sub-biclique optimum (total size 6), matching the paper's worked example exactly. - Julia parity fixture `tests/data/jl/biclique_cover.json` and its Rust parity test are dropped; the Julia package (Go6Cf) has the same OR-cover bug so its fixture would disagree with the fixed Rust semantics. Generator note explains the divergence. - New `BMF → BicliqueCover` reduction: matrix-to-biadjacency, rank passthrough, with `extract_solution` transposing the right-vertex half from vertex-major to biclique-row-major. Verified by closed-loop tests on all-ones rank-1, identity rank-2, and an infeasibility test on identity rank-1 (both problems now infeasible, consistent). - Paper updated: BicliqueCover problem-def now states the sub-biclique requirement explicitly; new `reduction-rule("BMF", "BicliqueCover")` entry added with the Monson–Pullman–Rees equivalence and the variable layout transpose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 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> * fix: tighten BMF and BicliqueCover edge cases - handle rank-zero exact BMF products against the full target matrix - make the BicliqueCover test helper reject pseudo-bicliques - include biclique count in complexity and use exact BMF target shape overhead --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ff8ae70 commit 9ff62f0

21 files changed

Lines changed: 638 additions & 433 deletions

docs/paper/reductions.typ

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5597,7 +5597,7 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
55975597
let nc = x.instance.n
55985598
let k = x.instance.k
55995599
let A = x.instance.matrix
5600-
let dH = metric-value(x.optimal_value)
5600+
let fs = metric-value(x.optimal_value)
56015601
// Decode B and C from optimal config
56025602
// Config layout: B is m*k values, then C is k*n values
56035603
let cfg = x.optimal_config
@@ -5609,11 +5609,11 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
56095609
let fmt-mat(m) = math.mat(..m.map(row => row.map(v => $#v$)))
56105610
[
56115611
#problem-def("BMF")[
5612-
Given an $m times n$ boolean matrix $A$ and rank $k$, find boolean matrices $B in {0,1}^(m times k)$ and $C in {0,1}^(k times n)$ minimizing the Hamming distance $d_H (A, B circle.tiny C)$, where the boolean product $(B circle.tiny C)_(i j) = or.big_ell (B_(i ell) and C_(ell j))$.
5612+
Given an $m times n$ boolean matrix $A$ and rank $k$, find boolean matrices $B in {0,1}^(m times k)$ and $C in {0,1}^(k times n)$ satisfying $B circle.tiny C = A$ and minimizing $|B|_1 + |C|_1$ (the total number of $1$s in $B$ and $C$), where the boolean product $(B circle.tiny C)_(i j) = or.big_ell (B_(i ell) and C_(ell j))$. An instance is infeasible when no exact factorization of rank $k$ exists.
56135613
][
5614-
Boolean Matrix Factorization decomposes binary data into interpretable boolean factors, unlike real-valued SVD which loses the discrete structure. NP-hard even to approximate, BMF arises in data mining, text classification, and role-based access control where factors correspond to latent binary features. Practical algorithms use greedy rank-1 extraction or alternating fixed-point methods. The best known exact algorithm runs in $O^*(2^(m k + k n))$ by brute-force search over $B$ and $C$#footnote[No algorithm improving on brute-force enumeration is known for general BMF.].
5614+
Boolean Matrix Factorization decomposes binary data into interpretable boolean factors, unlike real-valued SVD which loses the discrete structure. Deciding whether an exact factorization of a given rank exists is NP-complete (Orlin 1977); the minimum rank is the _Boolean rank_ of $A$, which coincides with the biclique edge cover number of the bipartite graph whose biadjacency matrix is $A$ (Monson, Pullman, Rees 1995). BMF arises in data mining, text classification, and role-based access control where factors correspond to latent binary features. The best known exact algorithm runs in $O^*(2^(m k + k n))$ by brute-force search over $B$ and $C$#footnote[No algorithm improving on brute-force enumeration is known for general exact BMF.].
56155615

5616-
*Example.* Let $A = #fmt-mat(A-int)$ and $k = #k$. Set $B = #fmt-mat(B)$ and $C = #fmt-mat(C)$. Then $B circle.tiny C = #fmt-mat(A-int) = A$, achieving Hamming distance $d_H = #dH$ (exact factorization). The two boolean factors capture overlapping row/column patterns: factor 1 selects rows ${1, 2}$ and columns ${1, 2}$; factor 2 selects rows ${2, 3}$ and columns ${2, 3}$.
5616+
*Example.* Let $A = #fmt-mat(A-int)$ and $k = #k$. Set $B = #fmt-mat(B)$ and $C = #fmt-mat(C)$. Then $B circle.tiny C = #fmt-mat(A-int) = A$, so the factorization is exact with total factor size $|B|_1 + |C|_1 = #fs$. The two boolean factors capture overlapping row/column patterns: factor 1 selects rows ${1, 2}$ and columns ${1, 2}$; factor 2 selects rows ${2, 3}$ and columns ${2, 3}$.
56175617

56185618
#pred-commands(
56195619
"pred create --example BMF -o bmf.json",
@@ -5778,9 +5778,9 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V|
57785778
let total-size = metric-value(sol.metric)
57795779
[
57805780
#problem-def("BicliqueCover")[
5781-
Given a bipartite graph $G = (L, R, E)$ and integer $k$, find $k$ bicliques $(L_1, R_1), dots, (L_k, R_k)$ that cover all edges ($E subset.eq union.big_i L_i times R_i$) while minimizing the total size $sum_i (|L_i| + |R_i|)$.
5781+
Given a bipartite graph $G = (L, R, E)$ and integer $k$, find $k$ *sub-bicliques* of $G$, $(L_1, R_1), dots, (L_k, R_k)$ with $L_i times R_i subset.eq E$ for every $i$, whose edge sets jointly cover $E$ — i.e. $E = union.big_i L_i times R_i$. Minimize the total size $sum_i (|L_i| + |R_i|)$. A configuration that places vertices into a biclique $i$ for which $L_i times R_i$ is not a subset of $E$ (a "biclique" spanning non-edges of $G$) is infeasible.
57825782
][
5783-
Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipartite graph as a Boolean sum of rank-1 binary matrices, connecting it to Boolean matrix rank and nondeterministic communication complexity. Applications include data compression, database optimization (covering queries with materialized views), and bioinformatics (gene expression biclustering). NP-hard even for fixed $k >= 2$. The best known algorithm runs in $O^*(2^(|L| + |R|))$ by brute-force enumeration#footnote[No algorithm improving on brute-force enumeration is known for general Biclique Cover.].
5783+
Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipartite graph exactly as a Boolean sum of rank-1 binary matrices (Monson, Pullman, Rees 1995), so the minimum $k$ for which a cover exists equals the _Boolean rank_ of $M$. The problem connects to Boolean matrix factorization, nondeterministic communication complexity, and role-based access control; applications include database optimization (covering queries with materialized views) and bioinformatics (gene expression biclustering). NP-complete already for fixed $k >= 2$. The best known algorithm runs in $O^*(2^(|L| + |R|))$ by brute-force enumeration#footnote[No algorithm improving on brute-force enumeration is known for general Biclique Cover.].
57845784

57855785
*Example.* Consider $G = (L, R, E)$ with $L = {#range(left-size).map(i => $ell_#(i + 1)$).join(", ")}$, $R = {#range(right-size).map(i => $r_#(i + 1)$).join(", ")}$, and edges $E = {#bip-edges.map(e => $(ell_#(e.at(0) + 1), r_#(e.at(1) + 1))$).join(", ")}$. A biclique cover with $k = #k$: $(L_1, R_1) = ({ell_1}, {r_1, r_2})$ covering edges ${(ell_1, r_1), (ell_1, r_2)}$, and $(L_2, R_2) = ({ell_2}, {r_2, r_3})$ covering ${(ell_2, r_2), (ell_2, r_3)}$. Total size $= (1+2) + (1+2) = #total-size$. Merging into a single biclique is impossible since $(ell_1, r_3) in.not E$.
57865786

@@ -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
][
@@ -14639,26 +14620,45 @@ The following reductions to Integer Linear Programming are straightforward formu
1463914620
// Matrix/encoding
1464014621

1464114622
#reduction-rule("BMF", "ILP")[
14642-
Split the witness into binary factor matrices $B$ and $C$, reconstruct their Boolean product with McCormick auxiliaries, and minimize the Hamming distance to the target matrix.
14623+
Split the witness into binary factor matrices $B$ and $C$, reconstruct their Boolean product with McCormick auxiliaries, pin each reconstructed entry to the target, and minimize the total factor weight.
1464314624
][
14644-
_Construction._ Variables: binary $b_(i,r)$, binary $c_(r,j)$, binary $p_(i,r,j)$ linearizing $b_(i,r) c_(r,j)$, binary $w_(i,j)$ for the reconstructed entry, and nonnegative error variables $e_(i,j)$. The ILP is:
14625+
_Construction._ Variables: binary $b_(i,r)$, binary $c_(r,j)$, binary $p_(i,r,j)$ linearizing $b_(i,r) c_(r,j)$, and binary $w_(i,j)$ for the reconstructed entry. The ILP is:
1464514626
$
14646-
min quad & sum_(i,j) e_(i,j) \
14627+
min quad & sum_(i,r) b_(i,r) + sum_(r,j) c_(r,j) \
1464714628
"subject to" quad & p_(i,r,j) <= b_(i,r) quad forall i, r, j \
1464814629
& p_(i,r,j) <= c_(r,j) quad forall i, r, j \
1464914630
& p_(i,r,j) >= b_(i,r) + c_(r,j) - 1 quad forall i, r, j \
1465014631
& w_(i,j) >= p_(i,r,j) quad forall i, r, j \
1465114632
& w_(i,j) <= sum_r p_(i,r,j) quad forall i, j \
14652-
& e_(i,j) >= A_(i,j) - w_(i,j) quad forall i, j \
14653-
& e_(i,j) >= w_(i,j) - A_(i,j) quad forall i, j \
14654-
& b_(i,r), c_(r,j), p_(i,r,j), w_(i,j) in {0, 1}, e_(i,j) in ZZ_(>=0).
14633+
& w_(i,j) = A_(i,j) quad forall i, j \
14634+
& b_(i,r), c_(r,j), p_(i,r,j), w_(i,j) in {0, 1}.
1465514635
$
1465614636

14657-
_Correctness._ ($arrow.r.double$) Any choice of factor matrices induces the same Boolean product and Hamming error in the ILP. ($arrow.l.double$) Any feasible ILP assignment determines factor matrices $B$ and $C$, and the linearization forces the objective to equal the Hamming distance between $A$ and $B dot C$.
14637+
_Correctness._ ($arrow.r.double$) Any exact factorization $B circle.tiny C = A$ gives a feasible ILP solution with objective equal to $|B|_1 + |C|_1$. ($arrow.l.double$) The McCormick constraints force $p_(i,r,j) = b_(i,r) dot c_(r,j)$; the $w$ constraints then force $w_(i,j) = or.big_r p_(i,r,j)$, so the equality $w_(i,j) = A_(i,j)$ is feasible exactly when $B circle.tiny C = A$. If no exact rank-$k$ factorization exists the ILP is infeasible, matching BMF's infeasibility signal.
1465814638

1465914639
_Solution extraction._ Output the flattened bits of $B$ followed by the flattened bits of $C$, discarding the reconstruction auxiliaries.
1466014640
]
1466114641

14642+
#reduction-rule("BMF", "BicliqueCover")[
14643+
Interpret the $m times n$ target matrix $A$ as the biadjacency matrix of a bipartite graph $G_A = (L, R, E)$ with $L = {1, dots, m}$, $R = {1, dots, n}$, and $(i, j) in E$ iff $A_(i j) = 1$, then reuse the same rank $k$.
14644+
][
14645+
_Construction._ Given an instance $(A, k)$ of BMF, emit the BicliqueCover instance $(G_A, k)$. The vertex-membership layout transposes the BMF factor layout: column $r$ of $B$ becomes the left side of biclique $r$, and row $r$ of $C$ becomes its right side.
14646+
14647+
_Correctness._ Each rank-1 factor $B_(dot,r) C_(r,dot)^top$ is the all-ones submatrix on ${i : B_(i,r) = 1} times {j : C_(r,j) = 1}$. Exactness of $B circle.tiny C = A$ is equivalent to (i) every such rectangle lying inside $E$ (sub-biclique of $G_A$), and (ii) the union of the $k$ rectangles exactly matching $E$ — which are precisely the two BicliqueCover feasibility conditions. The BMF objective $|B|_1 + |C|_1$ equals the total biclique size $sum_r (|L_r| + |R_r|)$, so the optimization objectives coincide (Monson, Pullman, Rees 1995).
14648+
14649+
_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.
14650+
]
14651+
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+
1466214662
#reduction-rule("ConsecutiveBlockMinimization", "ILP")[
1466314663
Permute the columns with a one-hot assignment and count row-wise block starts by detecting each 0-to-1 transition after permutation.
1466414664
][

scripts/jl/generate_testdata.jl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -717,15 +717,18 @@ function main()
717717
export_setcovering(doc_sc, "doc_3subsets"),
718718
]))
719719

720-
# BicliqueCover
721-
write_fixture("biclique_cover.json", model_fixture("BicliqueCover", [
722-
export_biclique_cover(doc_bc_graph, [1,2,3], 2, "doc_6vertex"),
723-
]))
724-
725-
# BMF
726-
write_fixture("bmf.json", model_fixture("BMF", [
727-
export_bmf(trues(3, 3), 2, "doc_3x3_ones"),
728-
]))
720+
# NOTE: BicliqueCover is no longer exported as a Julia parity fixture.
721+
# The Rust model enforces the classical sub-biclique semantics (each
722+
# biclique must be a complete bipartite subgraph of the input graph),
723+
# whereas `biclique_cover_evaluate` above implements the OR-cover
724+
# semantics used by the Julia package. Parity fixtures generated here
725+
# would therefore disagree with Rust on configurations that cover
726+
# non-edges.
727+
728+
# NOTE: BMF is no longer exported as a Julia parity fixture. The Rust model
729+
# was redefined as exact Boolean matrix factorization with a factor-size
730+
# objective; the old Hamming-distance semantics implemented by
731+
# `bmf_evaluate` / `export_bmf` below no longer match the Rust behavior.
729732

730733
# ── Export reduction fixtures ──
731734
println("Exporting reduction fixtures...")

src/models/algebraic/bmf.rs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
//! Boolean Matrix Factorization (BMF) problem implementation.
22
//!
3-
//! Given a boolean matrix A, find matrices B and C such that
4-
//! the boolean product B * C approximates A.
5-
//! The boolean product `(B * C)[i,j] = OR_k (B[i,k] AND C[k,j])`.
3+
//! Given a boolean matrix A and rank k, find boolean matrices B (m x k)
4+
//! and C (k x n) such that the boolean product B * C equals A exactly,
5+
//! minimizing the total number of 1s in B and C. Configs that do not
6+
//! produce an exact factorization evaluate to `Min(None)` (infeasible).
7+
//! The boolean product `(B * C)[i,j] = OR_r (B[i,r] AND C[r,j])`.
68
79
use crate::registry::{FieldInfo, ProblemSchemaEntry};
810
use crate::traits::Problem;
@@ -30,29 +32,25 @@ inventory::submit! {
3032
/// - B: m x k boolean matrix
3133
/// - C: k x n boolean matrix
3234
///
33-
/// Such that the Hamming distance between A and B*C is minimized.
35+
/// Such that `B * C = A` exactly, minimizing the total number of 1s in B and C.
36+
/// Configurations that do not yield an exact factorization are infeasible.
3437
///
3538
/// # Example
3639
///
3740
/// ```
3841
/// use problemreductions::models::algebraic::BMF;
3942
/// use problemreductions::{Problem, Solver, BruteForce};
4043
///
41-
/// // 2x2 identity matrix
44+
/// // 2x2 identity matrix — boolean rank 2
4245
/// let a = vec![
4346
/// vec![true, false],
4447
/// vec![false, true],
4548
/// ];
46-
/// let problem = BMF::new(a, 1);
49+
/// let problem = BMF::new(a, 2);
4750
///
4851
/// let solver = BruteForce::new();
49-
/// let solutions = solver.find_all_witnesses(&problem);
50-
///
51-
/// // Check the error
52-
/// for sol in &solutions {
53-
/// let error = problem.hamming_distance(sol);
54-
/// println!("Hamming error: {}", error);
55-
/// }
52+
/// let witness = solver.find_witness(&problem).unwrap();
53+
/// assert!(problem.is_exact(&witness));
5654
/// ```
5755
#[derive(Debug, Clone, Serialize, Deserialize)]
5856
pub struct BMF {
@@ -161,16 +159,14 @@ impl BMF {
161159
/// Compute the Hamming distance between the target and the product.
162160
pub fn hamming_distance(&self, config: &[usize]) -> usize {
163161
let (b, c) = self.extract_factors(config);
164-
let product = Self::boolean_product(&b, &c);
165-
166-
self.matrix
167-
.iter()
168-
.zip(product.iter())
169-
.map(|(a_row, p_row)| {
170-
a_row
171-
.iter()
172-
.zip(p_row.iter())
173-
.filter(|(a, p)| a != p)
162+
163+
(0..self.m)
164+
.map(|i| {
165+
(0..self.n)
166+
.filter(|&j| {
167+
let product_entry = (0..self.k).any(|r| b[i][r] && c[r][j]);
168+
self.matrix[i][j] != product_entry
169+
})
174170
.count()
175171
})
176172
.sum()
@@ -180,6 +176,11 @@ impl BMF {
180176
pub fn is_exact(&self, config: &[usize]) -> bool {
181177
self.hamming_distance(config) == 0
182178
}
179+
180+
/// Total number of 1s in B and C (the factor size to be minimized when exact).
181+
pub fn total_factor_size(&self, config: &[usize]) -> usize {
182+
config.iter().filter(|&&x| x == 1).count()
183+
}
183184
}
184185

185186
/// Compute the boolean matrix product.
@@ -213,9 +214,11 @@ impl Problem for BMF {
213214
}
214215

215216
fn evaluate(&self, config: &[usize]) -> Min<i32> {
216-
// Minimize Hamming distance between A and B*C.
217-
// All configurations are valid -- the distance is the objective.
218-
Min(Some(self.hamming_distance(config) as i32))
217+
// Feasible iff B*C = A exactly; objective is total factor size (|B| + |C| in 1s).
218+
if self.hamming_distance(config) != 0 {
219+
return Min(None);
220+
}
221+
Min(Some(self.total_factor_size(config) as i32))
219222
}
220223

221224
fn variant() -> Vec<(&'static str, &'static str)> {
@@ -239,8 +242,10 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
239242
],
240243
2,
241244
)),
242-
optimal_config: vec![0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0],
243-
optimal_value: serde_json::json!(0),
245+
// B = [[1,0],[1,1],[0,1]] (row-major: 1,0,1,1,0,1), C = [[1,1,0],[0,1,1]] (row-major: 1,1,0,0,1,1).
246+
// Total 1s: 4 in B + 4 in C = 8, and B * C = A exactly.
247+
optimal_config: vec![1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1],
248+
optimal_value: serde_json::json!(8),
244249
}]
245250
}
246251

0 commit comments

Comments
 (0)