problemreductions/rules/kcoloring_bicliquecover.rs
1//! Reduction from KColoring to BicliqueCover via a guard-gadget construction.
2//!
3//! Self-contained gadget: given a KColoring instance `(G, q)` with `n = |V|`
4//! and `m = |E|`, build a bipartite graph `H = (L, R, F)` with `2n` left
5//! vertices and `2n` right vertices, and ask for a biclique cover of `H`
6//! using `n + q` sub-bicliques. The construction is designed so that
7//! exactly `n` of the bicliques are forced to cover guard-anchor edges,
8//! leaving at most `q` bicliques to cover the `n` diagonal edges
9//! `(a_v, b_v)`. These remaining bicliques behave as color classes: two
10//! source vertices may share one only when they are nonadjacent in `G`.
11//!
12//! See issue #1058 for the full proof sketch.
13//!
14//! ## Vertex layout
15//!
16//! For `v in 0..n`, the gadget produces four target vertices:
17//!
18//! - Left partition (size `2n`):
19//! - `a_v` at local index `v`
20//! - `g_v` at local index `n + v`
21//! - Right partition (size `2n`):
22//! - `b_v` at local index `v`
23//! - `h_v` at local index `n + v`
24//!
25//! In unified vertex space (used by `BicliqueCover::dims()`):
26//!
27//! - `a_v` -> `v`
28//! - `g_v` -> `n + v`
29//! - `b_v` -> `2n + v` (i.e. `left_size + v`)
30//! - `h_v` -> `3n + v` (i.e. `left_size + n + v`)
31
32use crate::models::graph::{BicliqueCover, KColoring};
33use crate::reduction;
34use crate::rules::traits::{ReduceTo, ReductionResult};
35use crate::topology::{BipartiteGraph, Graph, SimpleGraph};
36use crate::variant::KN;
37use std::collections::BTreeSet;
38
39/// Result of reducing KColoring to BicliqueCover.
40#[derive(Debug, Clone)]
41pub struct ReductionKColoringToBicliqueCover {
42 target: BicliqueCover,
43 /// Number of source vertices `n`. Stored so `extract_solution` can locate
44 /// the diagonal indices of each source vertex without re-reading the
45 /// reduction parameters.
46 num_vertices: usize,
47 /// Number of source colors `q`. Used as the upper bound on the number of
48 /// color bicliques recovered during extraction.
49 num_colors: usize,
50}
51
52impl ReductionResult for ReductionKColoringToBicliqueCover {
53 type Source = KColoring<KN, SimpleGraph>;
54 type Target = BicliqueCover;
55
56 fn target_problem(&self) -> &BicliqueCover {
57 &self.target
58 }
59
60 /// Recover a source coloring from a BicliqueCover witness.
61 ///
62 /// For each source vertex `v`, find any biclique `r` that contains both
63 /// the left vertex `a_v` and the right vertex `b_v`. The diagonal edge
64 /// `(a_v, b_v)` must lie in some biclique of any valid cover. Compact
65 /// the distinct diagonal-covering biclique indices into colors
66 /// `0..q-1` in first-seen order; vertices whose biclique is one of
67 /// these get the compacted color. By the correctness proof, a valid
68 /// cover yields at most `q` such distinct bicliques, so the result is a
69 /// proper `q`-coloring of the source.
70 ///
71 /// If the witness is invalid (e.g. some diagonal edge is uncovered),
72 /// the extracted entry for `v` falls back to color `0`. Validation
73 /// downstream is the responsibility of `source.is_valid_solution`.
74 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
75 let n = self.num_vertices;
76 let k = self.target.k();
77 let left_size = 2 * n;
78
79 // For each source vertex v, find the first biclique r that contains
80 // both a_v (unified index v) and b_v (unified index left_size + v).
81 let mut diagonal_biclique = vec![None; n];
82 for (v, slot) in diagonal_biclique.iter_mut().enumerate() {
83 let a_v = v;
84 let b_v = left_size + v;
85 for r in 0..k {
86 let a_idx = a_v * k + r;
87 let b_idx = b_v * k + r;
88 if target_solution.get(a_idx).copied().unwrap_or(0) == 1
89 && target_solution.get(b_idx).copied().unwrap_or(0) == 1
90 {
91 *slot = Some(r);
92 break;
93 }
94 }
95 }
96
97 // Compact distinct biclique indices into colors 0..q-1 in first-seen order.
98 let mut color_of_biclique: std::collections::HashMap<usize, usize> =
99 std::collections::HashMap::new();
100 let mut coloring = vec![0usize; n];
101 for (v, slot) in diagonal_biclique.iter().enumerate() {
102 if let Some(r) = *slot {
103 let next_color = color_of_biclique.len();
104 let color = *color_of_biclique.entry(r).or_insert(next_color);
105 // Clamp into [0, q-1]: if the witness exceeds q distinct
106 // diagonal bicliques (which a valid cover never does) keep
107 // the entry in range so the downstream validator can
108 // simply reject it as an improper coloring.
109 coloring[v] = if self.num_colors == 0 {
110 0
111 } else {
112 color.min(self.num_colors - 1)
113 };
114 }
115 }
116 coloring
117 }
118}
119
120#[reduction(
121 overhead = {
122 num_vertices = "4 * num_vertices",
123 num_edges = "2 * num_vertices * (num_vertices - 1) - 4 * num_edges + 3 * num_vertices",
124 rank = "num_vertices + num_colors",
125 }
126)]
127impl ReduceTo<BicliqueCover> for KColoring<KN, SimpleGraph> {
128 type Result = ReductionKColoringToBicliqueCover;
129
130 fn reduce_to(&self) -> Self::Result {
131 let n = self.graph().num_vertices();
132 let q = self.num_colors();
133
134 // Build the set of source edges as an undirected lookup so the
135 // construction can skip endpoints {u,v} in E in O(1).
136 let mut source_edges: BTreeSet<(usize, usize)> = BTreeSet::new();
137 for (u, v) in self.graph().edges() {
138 let (a, b) = if u <= v { (u, v) } else { (v, u) };
139 source_edges.insert((a, b));
140 }
141 let has_source_edge = |u: usize, v: usize| -> bool {
142 if u == v {
143 return false;
144 }
145 let (a, b) = if u <= v { (u, v) } else { (v, u) };
146 source_edges.contains(&(a, b))
147 };
148
149 // Target vertex layout (bipartite-local indices):
150 // left: a_v at v, g_v at n + v
151 // right: b_v at v, h_v at n + v
152 let a_left = |v: usize| -> usize { v };
153 let g_left = |v: usize| -> usize { n + v };
154 let b_right = |v: usize| -> usize { v };
155 let h_right = |v: usize| -> usize { n + v };
156
157 let mut edges: Vec<(usize, usize)> = Vec::new();
158
159 // 1. Diagonal edges (a_v, b_v).
160 for v in 0..n {
161 edges.push((a_left(v), b_right(v)));
162 }
163
164 // 2. Compatibility edges (a_u, b_v) for ordered u != v with {u,v} not in E.
165 for u in 0..n {
166 for v in 0..n {
167 if u == v {
168 continue;
169 }
170 if !has_source_edge(u, v) {
171 edges.push((a_left(u), b_right(v)));
172 }
173 }
174 }
175
176 // 3. Guard-anchor edges (a_v, h_v) and (g_v, h_v).
177 for v in 0..n {
178 edges.push((a_left(v), h_right(v)));
179 edges.push((g_left(v), h_right(v)));
180 }
181
182 // 4. Guard compatibility edges (g_v, b_w) for v != w with {v,w} not in E.
183 for v in 0..n {
184 for w in 0..n {
185 if v == w {
186 continue;
187 }
188 if !has_source_edge(v, w) {
189 edges.push((g_left(v), b_right(w)));
190 }
191 }
192 }
193
194 let left_size = 2 * n;
195 let right_size = 2 * n;
196 let target = BicliqueCover::new(BipartiteGraph::new(left_size, right_size, edges), n + q);
197
198 ReductionKColoringToBicliqueCover {
199 target,
200 num_vertices: n,
201 num_colors: q,
202 }
203 }
204}
205
206/// Build the canonical forward witness described in the issue.
207///
208/// For each source vertex `v` (with `v in 0..n`), create one guard biclique
209///
210/// ```text
211/// G_v = ({a_v, g_v}, {h_v} ∪ {b_w : w != v, {v,w} ∉ E})
212/// ```
213///
214/// and for each color class `C ⊆ V`, create one color biclique
215///
216/// ```text
217/// C_color = ({a_v : v in C}, {b_v : v in C}).
218/// ```
219///
220/// Returns a vertex-major BicliqueCover configuration of length
221/// `4n * (n + q)` (i.e. `num_vertices * rank`).
222///
223/// `coloring[v]` must be in `0..q`. The order of color bicliques is the
224/// order of first appearance of each color along `0..n`, so unused colors
225/// at the tail produce empty bicliques.
226#[cfg(any(test, feature = "example-db"))]
227pub(crate) fn forward_witness(
228 source: &KColoring<KN, SimpleGraph>,
229 coloring: &[usize],
230) -> Vec<usize> {
231 let n = source.graph().num_vertices();
232 let q = source.num_colors();
233 let k = n + q;
234 let left_size = 2 * n;
235 let num_vertices = 4 * n;
236 let mut config = vec![0usize; num_vertices * k];
237
238 let set_member = |config: &mut Vec<usize>, vertex: usize, biclique: usize| {
239 config[vertex * k + biclique] = 1;
240 };
241
242 // Edge-membership lookup for source edges (undirected).
243 let mut source_edges: BTreeSet<(usize, usize)> = BTreeSet::new();
244 for (u, v) in source.graph().edges() {
245 let (a, b) = if u <= v { (u, v) } else { (v, u) };
246 source_edges.insert((a, b));
247 }
248 let has_source_edge = |u: usize, v: usize| -> bool {
249 if u == v {
250 return false;
251 }
252 let (a, b) = if u <= v { (u, v) } else { (v, u) };
253 source_edges.contains(&(a, b))
254 };
255
256 // Guard bicliques: biclique index v (for v in 0..n).
257 for v in 0..n {
258 let biclique = v;
259 // Left: a_v (unified index v), g_v (unified index n + v).
260 set_member(&mut config, v, biclique);
261 set_member(&mut config, n + v, biclique);
262 // Right: h_v (unified index left_size + n + v) and b_w for nonadjacent w != v.
263 set_member(&mut config, left_size + n + v, biclique); // h_v
264 for w in 0..n {
265 if w != v && !has_source_edge(v, w) {
266 set_member(&mut config, left_size + w, biclique); // b_w
267 }
268 }
269 }
270
271 // Color bicliques: biclique index n + c for color c (in first-seen order).
272 let mut color_to_biclique: std::collections::HashMap<usize, usize> =
273 std::collections::HashMap::new();
274 for (v, &c) in coloring.iter().enumerate().take(n) {
275 let next_slot = color_to_biclique.len();
276 let slot = *color_to_biclique.entry(c).or_insert(next_slot);
277 let biclique = n + slot;
278 // Left: a_v (unified index v).
279 set_member(&mut config, v, biclique);
280 // Right: b_v (unified index left_size + v).
281 set_member(&mut config, left_size + v, biclique);
282 }
283
284 config
285}
286
287#[cfg(feature = "example-db")]
288pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
289 use crate::export::SolutionPair;
290
291 vec![crate::example_db::specs::RuleExampleSpec {
292 id: "kcoloring_to_bicliquecover",
293 build: || {
294 // P_2 with q = 2: vertices {0, 1}, one edge (0, 1).
295 // A valid 2-coloring is (0, 1). Target has 8 vertices and rank 4,
296 // small enough to keep the canonical bundle compact.
297 let source = KColoring::<KN, _>::with_k(SimpleGraph::new(2, vec![(0, 1)]), 2);
298 let coloring = vec![0usize, 1usize];
299 let target_config = forward_witness(&source, &coloring);
300 crate::example_db::specs::rule_example_with_witness::<_, BicliqueCover>(
301 source,
302 SolutionPair {
303 source_config: coloring,
304 target_config,
305 },
306 )
307 },
308 }]
309}
310
311#[cfg(test)]
312#[path = "../unit_tests/rules/kcoloring_bicliquecover.rs"]
313mod tests;