Skip to main content

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;