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 + min(q, n)` 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//! The paper contains the full proof, including native loops and repeated edges.
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    fn extract_solution(
72        &self,
73        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
74    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
75        let value =
76            crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
77        if value.0.is_none() {
78            return Err(crate::rules::ExtractionError::invalid(
79                "target configuration is not a biclique cover",
80            ));
81        }
82
83        Ok({
84            let n = self.num_vertices;
85            let k = self.target.k();
86            let left_size = 2 * n;
87
88            // For each source vertex v, find the first biclique r that contains
89            // both a_v (unified index v) and b_v (unified index left_size + v).
90            let mut diagonal_biclique = Vec::with_capacity(n);
91            for v in 0..n {
92                let a_v = v;
93                let b_v = left_size + v;
94                let biclique = (0..k)
95                    .find(|&r| target_solution[r][a_v] && target_solution[r][b_v])
96                    .ok_or_else(|| {
97                        crate::rules::ExtractionError::invalid(format!(
98                            "target cover leaves diagonal gadget edge {v} uncovered"
99                        ))
100                    })?;
101                diagonal_biclique.push(biclique);
102            }
103
104            // Compact distinct biclique indices into colors 0..q-1 in first-seen order.
105            let mut color_of_biclique: std::collections::HashMap<usize, usize> =
106                std::collections::HashMap::new();
107            let mut coloring = Vec::with_capacity(n);
108            for biclique in diagonal_biclique {
109                let next_color = color_of_biclique.len();
110                let color = *color_of_biclique.entry(biclique).or_insert(next_color);
111                if color >= self.num_colors {
112                    return Err(crate::rules::ExtractionError::invalid(format!(
113                        "target cover uses more than {} diagonal bicliques",
114                        self.num_colors
115                    )));
116                }
117                coloring.push(color);
118            }
119            coloring
120        })
121    }
122}
123
124#[reduction(
125    transform = upper_bound {
126        left_size = "2 * num_vertices + 1",
127        num_vertices = "4 * num_vertices + 2",
128        num_edges = "2 * num_vertices^2 + num_vertices + 1",
129        rank = "2 * num_vertices",
130        right_size = "2 * num_vertices + 1",
131    }
132)]
133impl ReduceTo<BicliqueCover> for KColoring<KN, SimpleGraph> {
134    type Result = ReductionKColoringToBicliqueCover;
135
136    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
137        let n = self.graph().num_vertices();
138        // Relabeling the used colors preserves feasibility with at most n colors.
139        // Native node storage bounds make 4*n and n+min(q,n) fit usize.
140        let q = self.num_colors().min(n);
141        let native_edges = self.graph().edges();
142        if native_edges.iter().any(|&(u, v)| u == v) {
143            // A loop cannot be properly colored. A single edge cannot be
144            // covered with zero bicliques, giving a fixed NO instance.
145            return Ok(ReductionKColoringToBicliqueCover {
146                target: BicliqueCover::new(BipartiteGraph::new(1, 1, vec![(0, 0)]), 0),
147                num_vertices: n,
148                num_colors: q,
149            });
150        }
151
152        // Build the set of source edges as an undirected lookup so the
153        // construction can test endpoints {u,v} in E in O(log m).
154        let mut source_edges: BTreeSet<(usize, usize)> = BTreeSet::new();
155        for (u, v) in native_edges {
156            let (a, b) = if u <= v { (u, v) } else { (v, u) };
157            source_edges.insert((a, b));
158        }
159        let has_source_edge = |u: usize, v: usize| -> bool {
160            if u == v {
161                return false;
162            }
163            let (a, b) = if u <= v { (u, v) } else { (v, u) };
164            source_edges.contains(&(a, b))
165        };
166
167        // Target vertex layout (bipartite-local indices):
168        //   left:  a_v at v,         g_v at n + v
169        //   right: b_v at v,         h_v at n + v
170        let a_left = |v: usize| -> usize { v };
171        let g_left = |v: usize| -> usize { n + v };
172        let b_right = |v: usize| -> usize { v };
173        let h_right = |v: usize| -> usize { n + v };
174
175        let mut edges: Vec<(usize, usize)> = Vec::new();
176
177        // 1. Diagonal edges (a_v, b_v).
178        for v in 0..n {
179            edges.push((a_left(v), b_right(v)));
180        }
181
182        // 2. Compatibility edges (a_u, b_v) for ordered u != v with {u,v} not in E.
183        for u in 0..n {
184            for v in 0..n {
185                if u == v {
186                    continue;
187                }
188                if !has_source_edge(u, v) {
189                    edges.push((a_left(u), b_right(v)));
190                }
191            }
192        }
193
194        // 3. Guard-anchor edges (a_v, h_v) and (g_v, h_v).
195        for v in 0..n {
196            edges.push((a_left(v), h_right(v)));
197            edges.push((g_left(v), h_right(v)));
198        }
199
200        // 4. Guard compatibility edges (g_v, b_w) for v != w with {v,w} not in E.
201        for v in 0..n {
202            for w in 0..n {
203                if v == w {
204                    continue;
205                }
206                if !has_source_edge(v, w) {
207                    edges.push((g_left(v), b_right(w)));
208                }
209            }
210        }
211
212        let left_size = 2 * n;
213        let right_size = 2 * n;
214        let target = BicliqueCover::new(BipartiteGraph::new(left_size, right_size, edges), n + q);
215
216        Ok(ReductionKColoringToBicliqueCover {
217            target,
218            num_vertices: n,
219            num_colors: q,
220        })
221    }
222}
223
224/// Build the canonical forward witness described in the issue.
225///
226/// For each source vertex `v` (with `v in 0..n`), create one guard biclique
227///
228/// ```text
229/// G_v = ({a_v, g_v}, {h_v} ∪ {b_w : w != v, {v,w} ∉ E})
230/// ```
231///
232/// and for each color class `C ⊆ V`, create one color biclique
233///
234/// ```text
235/// C_color = ({a_v : v in C}, {b_v : v in C}).
236/// ```
237///
238/// Returns one membership row per biclique. Each row has one Boolean entry
239/// per target vertex.
240///
241/// `coloring[v]` must be in `0..q`. The order of color bicliques is the
242/// order of first appearance of each color along `0..n`, so unused colors
243/// at the tail produce empty bicliques.
244#[cfg(any(test, feature = "example-db"))]
245pub(crate) fn forward_witness(
246    source: &KColoring<KN, SimpleGraph>,
247    coloring: &[usize],
248) -> Vec<Vec<bool>> {
249    let n = source.graph().num_vertices();
250    let q = source.num_colors().min(n);
251    let k = n + q;
252    let left_size = 2 * n;
253    let num_vertices = 4 * n;
254    let mut config = vec![vec![false; num_vertices]; k];
255
256    let set_member = |config: &mut Vec<Vec<bool>>, vertex: usize, biclique: usize| {
257        config[biclique][vertex] = true;
258    };
259
260    // Edge-membership lookup for source edges (undirected).
261    let mut source_edges: BTreeSet<(usize, usize)> = BTreeSet::new();
262    for (u, v) in source.graph().edges() {
263        let (a, b) = if u <= v { (u, v) } else { (v, u) };
264        source_edges.insert((a, b));
265    }
266    let has_source_edge = |u: usize, v: usize| -> bool {
267        if u == v {
268            return false;
269        }
270        let (a, b) = if u <= v { (u, v) } else { (v, u) };
271        source_edges.contains(&(a, b))
272    };
273
274    // Guard bicliques: biclique index v (for v in 0..n).
275    for v in 0..n {
276        let biclique = v;
277        // Left: a_v (unified index v), g_v (unified index n + v).
278        set_member(&mut config, v, biclique);
279        set_member(&mut config, n + v, biclique);
280        // Right: h_v (unified index left_size + n + v) and b_w for nonadjacent w != v.
281        set_member(&mut config, left_size + n + v, biclique); // h_v
282        for w in 0..n {
283            if w != v && !has_source_edge(v, w) {
284                set_member(&mut config, left_size + w, biclique); // b_w
285            }
286        }
287    }
288
289    // Color bicliques: biclique index n + c for color c (in first-seen order).
290    let mut color_to_biclique: std::collections::HashMap<usize, usize> =
291        std::collections::HashMap::new();
292    for (v, &c) in coloring.iter().enumerate().take(n) {
293        let next_slot = color_to_biclique.len();
294        let slot = *color_to_biclique.entry(c).or_insert(next_slot);
295        let biclique = n + slot;
296        // Left: a_v (unified index v).
297        set_member(&mut config, v, biclique);
298        // Right: b_v (unified index left_size + v).
299        set_member(&mut config, left_size + v, biclique);
300    }
301
302    config
303}
304
305#[cfg(feature = "example-db")]
306pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
307    use crate::export::SolutionPair;
308
309    vec![crate::example_db::specs::RuleExampleSpec {
310        id: "kcoloring_to_bicliquecover",
311        build: || {
312            // P_2 with q = 2: vertices {0, 1}, one edge (0, 1).
313            // A valid 2-coloring is (0, 1). Target has 8 vertices and rank 4,
314            // small enough to keep the canonical bundle compact.
315            let source = KColoring::<KN, _>::with_k(SimpleGraph::new(2, vec![(0, 1)]), 2);
316            let coloring = vec![0usize, 1usize];
317            let target_config = forward_witness(&source, &coloring);
318            crate::example_db::specs::rule_example_with_witness::<_, BicliqueCover>(
319                source,
320                SolutionPair {
321                    source_config: serde_json::json!(coloring),
322                    target_config: serde_json::to_value(target_config)
323                        .expect("solution serialization must succeed"),
324                },
325            )
326        },
327    }]
328}
329
330#[cfg(test)]
331#[path = "../unit_tests/rules/kcoloring_bicliquecover.rs"]
332mod tests;