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