Skip to main content

problemreductions/rules/
decisionminimumvertexcover_hamiltoniancircuit.rs

1//! Reduction from Decision Minimum Vertex Cover to Hamiltonian Circuit.
2//!
3//! This implements the gadget construction from Garey & Johnson, Theorem 3.4,
4//! on the unit-weight `Decision<MinimumVertexCover<SimpleGraph, i32>>` model.
5
6use crate::models::decision::Decision;
7use crate::models::graph::{HamiltonianCircuit, MinimumVertexCover};
8use crate::reduction;
9use crate::rules::traits::{ReduceTo, ReductionResult};
10use crate::topology::{Graph, SimpleGraph};
11use crate::traits::Problem;
12use std::collections::BTreeSet;
13
14#[derive(Debug, Clone)]
15enum ConstructionKind {
16    FixedYes { source_cover: Vec<usize> },
17    FixedNo { num_source_vertices: usize },
18    Theorem(TheoremConstruction),
19}
20
21#[derive(Debug, Clone)]
22struct TheoremConstruction {
23    num_source_vertices: usize,
24    selector_count: usize,
25    edges: Vec<(usize, usize)>,
26    incident_edges: Vec<Vec<usize>>,
27}
28
29impl TheoremConstruction {
30    fn active_vertices(&self) -> impl Iterator<Item = usize> + '_ {
31        self.incident_edges
32            .iter()
33            .enumerate()
34            .filter(|(_, edges)| !edges.is_empty())
35            .map(|(v, _)| v)
36    }
37
38    fn gadget_base(&self, edge_idx: usize) -> usize {
39        self.selector_count + 12 * edge_idx
40    }
41
42    fn gadget_vertex(&self, edge_idx: usize, vertex: usize, position: usize) -> usize {
43        let (u, v) = self.edges[edge_idx];
44        let side = if vertex == u {
45            0
46        } else if vertex == v {
47            1
48        } else {
49            panic!(
50                "vertex {vertex} is not incident on edge {:?}",
51                self.edges[edge_idx]
52            );
53        };
54
55        self.gadget_base(edge_idx) + side * 6 + (position - 1)
56    }
57
58    fn path_endpoints(&self, vertex: usize) -> Option<(usize, usize)> {
59        let incident = self.incident_edges.get(vertex)?;
60        let first = *incident.first()?;
61        let last = *incident.last()?;
62        Some((
63            self.gadget_vertex(first, vertex, 1),
64            self.gadget_vertex(last, vertex, 6),
65        ))
66    }
67
68    fn covers_all_edges(&self, selected: &[usize]) -> bool {
69        self.edges
70            .iter()
71            .all(|&(u, v)| selected.get(u) == Some(&1) || selected.get(v) == Some(&1))
72    }
73
74    #[cfg(any(test, feature = "example-db"))]
75    fn exact_selected_vertices(&self, source_cover: &[usize]) -> Option<Vec<usize>> {
76        if source_cover.len() != self.num_source_vertices || !self.covers_all_edges(source_cover) {
77            return None;
78        }
79
80        let mut selected: Vec<usize> = self
81            .active_vertices()
82            .filter(|&v| source_cover[v] == 1)
83            .collect();
84
85        if selected.len() > self.selector_count {
86            return None;
87        }
88
89        for v in self.active_vertices() {
90            if selected.len() == self.selector_count {
91                break;
92            }
93            if source_cover[v] == 0 {
94                selected.push(v);
95            }
96        }
97
98        (selected.len() == self.selector_count).then_some(selected)
99    }
100
101    #[cfg(any(test, feature = "example-db"))]
102    fn gadget_segment(
103        &self,
104        edge_idx: usize,
105        vertex: usize,
106        selected_exact: &BTreeSet<usize>,
107    ) -> Vec<usize> {
108        let (u, v) = self.edges[edge_idx];
109        let other = if vertex == u {
110            v
111        } else if vertex == v {
112            u
113        } else {
114            panic!(
115                "vertex {vertex} is not incident on edge {:?}",
116                self.edges[edge_idx]
117            );
118        };
119
120        if selected_exact.contains(&other) {
121            return (1..=6)
122                .map(|position| self.gadget_vertex(edge_idx, vertex, position))
123                .collect();
124        }
125
126        if vertex == u {
127            vec![
128                self.gadget_vertex(edge_idx, u, 1),
129                self.gadget_vertex(edge_idx, u, 2),
130                self.gadget_vertex(edge_idx, u, 3),
131                self.gadget_vertex(edge_idx, v, 1),
132                self.gadget_vertex(edge_idx, v, 2),
133                self.gadget_vertex(edge_idx, v, 3),
134                self.gadget_vertex(edge_idx, v, 4),
135                self.gadget_vertex(edge_idx, v, 5),
136                self.gadget_vertex(edge_idx, v, 6),
137                self.gadget_vertex(edge_idx, u, 4),
138                self.gadget_vertex(edge_idx, u, 5),
139                self.gadget_vertex(edge_idx, u, 6),
140            ]
141        } else {
142            vec![
143                self.gadget_vertex(edge_idx, v, 1),
144                self.gadget_vertex(edge_idx, v, 2),
145                self.gadget_vertex(edge_idx, v, 3),
146                self.gadget_vertex(edge_idx, u, 1),
147                self.gadget_vertex(edge_idx, u, 2),
148                self.gadget_vertex(edge_idx, u, 3),
149                self.gadget_vertex(edge_idx, u, 4),
150                self.gadget_vertex(edge_idx, u, 5),
151                self.gadget_vertex(edge_idx, u, 6),
152                self.gadget_vertex(edge_idx, v, 4),
153                self.gadget_vertex(edge_idx, v, 5),
154                self.gadget_vertex(edge_idx, v, 6),
155            ]
156        }
157    }
158
159    #[cfg(any(test, feature = "example-db"))]
160    fn vertex_path(&self, vertex: usize, selected_exact: &BTreeSet<usize>) -> Vec<usize> {
161        let mut path = Vec::new();
162        for &edge_idx in &self.incident_edges[vertex] {
163            path.extend(self.gadget_segment(edge_idx, vertex, selected_exact));
164        }
165        path
166    }
167
168    #[cfg(any(test, feature = "example-db"))]
169    fn build_target_witness(&self, source_cover: &[usize]) -> Vec<usize> {
170        let Some(selected_vertices) = self.exact_selected_vertices(source_cover) else {
171            return Vec::new();
172        };
173
174        let selected_exact: BTreeSet<usize> = selected_vertices.iter().copied().collect();
175        let mut witness = Vec::with_capacity(self.selector_count + 12 * self.edges.len());
176
177        for (selector, &vertex) in selected_vertices.iter().enumerate() {
178            witness.push(selector);
179            witness.extend(self.vertex_path(vertex, &selected_exact));
180        }
181
182        witness
183    }
184
185    fn extract_solution(
186        &self,
187        target_problem: &HamiltonianCircuit<SimpleGraph>,
188        target_solution: &[usize],
189    ) -> Vec<usize> {
190        let mut source_cover = vec![0; self.num_source_vertices];
191        if !target_problem.evaluate(target_solution).0 {
192            return source_cover;
193        }
194
195        let mut positions = vec![usize::MAX; target_solution.len()];
196        for (idx, &vertex) in target_solution.iter().enumerate() {
197            if vertex >= positions.len() || positions[vertex] != usize::MAX {
198                return vec![0; self.num_source_vertices];
199            }
200            positions[vertex] = idx;
201        }
202
203        let len = target_solution.len();
204        let touches_selector = |vertex: usize| {
205            let idx = positions[vertex];
206            let prev = target_solution[(idx + len - 1) % len];
207            let next = target_solution[(idx + 1) % len];
208            prev < self.selector_count || next < self.selector_count
209        };
210
211        for vertex in self.active_vertices() {
212            let Some((start, end)) = self.path_endpoints(vertex) else {
213                continue;
214            };
215            if touches_selector(start) && touches_selector(end) {
216                source_cover[vertex] = 1;
217            }
218        }
219
220        let selected_count = source_cover.iter().filter(|&&x| x == 1).count();
221        if selected_count != self.selector_count || !self.covers_all_edges(&source_cover) {
222            return vec![0; self.num_source_vertices];
223        }
224
225        source_cover
226    }
227}
228
229/// Result of reducing Decision<MinimumVertexCover<SimpleGraph, i32>> to
230/// HamiltonianCircuit<SimpleGraph>.
231#[derive(Debug, Clone)]
232pub struct ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
233    target: HamiltonianCircuit<SimpleGraph>,
234    construction: ConstructionKind,
235}
236
237impl ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
238    #[cfg(any(test, feature = "example-db"))]
239    fn build_target_witness(&self, source_cover: &[usize]) -> Vec<usize> {
240        match &self.construction {
241            ConstructionKind::FixedYes { .. } => vec![0, 1, 2],
242            ConstructionKind::FixedNo { .. } => Vec::new(),
243            ConstructionKind::Theorem(construction) => {
244                construction.build_target_witness(source_cover)
245            }
246        }
247    }
248}
249
250impl ReductionResult for ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
251    type Source = Decision<MinimumVertexCover<SimpleGraph, i32>>;
252    type Target = HamiltonianCircuit<SimpleGraph>;
253
254    fn target_problem(&self) -> &Self::Target {
255        &self.target
256    }
257
258    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
259        match &self.construction {
260            ConstructionKind::FixedYes { source_cover } => {
261                if self.target.evaluate(target_solution).0 {
262                    source_cover.clone()
263                } else {
264                    vec![0; source_cover.len()]
265                }
266            }
267            ConstructionKind::FixedNo {
268                num_source_vertices,
269            } => vec![0; *num_source_vertices],
270            ConstructionKind::Theorem(construction) => {
271                construction.extract_solution(&self.target, target_solution)
272            }
273        }
274    }
275}
276
277fn normalize_edges(edges: Vec<(usize, usize)>) -> Vec<(usize, usize)> {
278    let mut normalized: Vec<_> = edges
279        .into_iter()
280        .map(|(u, v)| if u < v { (u, v) } else { (v, u) })
281        .collect();
282    normalized.sort_unstable();
283    normalized
284}
285
286fn insert_edge(edges: &mut BTreeSet<(usize, usize)>, a: usize, b: usize) {
287    let edge = if a < b { (a, b) } else { (b, a) };
288    edges.insert(edge);
289}
290
291#[reduction(
292    overhead = {
293        num_vertices = "12 * num_edges + k",
294        num_edges = "16 * num_edges - num_vertices + 2 * k * num_vertices",
295    }
296)]
297impl ReduceTo<HamiltonianCircuit<SimpleGraph>> for Decision<MinimumVertexCover<SimpleGraph, i32>> {
298    type Result = ReductionDecisionMinimumVertexCoverToHamiltonianCircuit;
299
300    fn reduce_to(&self) -> Self::Result {
301        let weights = self.inner().weights();
302        assert!(
303            weights.iter().all(|&weight| weight == 1),
304            "Garey-Johnson Theorem 3.4 requires unit vertex weights"
305        );
306
307        let num_source_vertices = self.inner().graph().num_vertices();
308        let raw_bound = *self.bound();
309        if raw_bound < 0 {
310            return ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
311                target: HamiltonianCircuit::new(SimpleGraph::path(3)),
312                construction: ConstructionKind::FixedNo {
313                    num_source_vertices,
314                },
315            };
316        }
317
318        let k = self.k();
319        let edges = normalize_edges(self.inner().graph().edges());
320        let mut incident_edges = vec![Vec::new(); num_source_vertices];
321        for (edge_idx, &(u, v)) in edges.iter().enumerate() {
322            incident_edges[u].push(edge_idx);
323            incident_edges[v].push(edge_idx);
324        }
325
326        let active_vertices: Vec<_> = incident_edges
327            .iter()
328            .enumerate()
329            .filter(|(_, incident)| !incident.is_empty())
330            .map(|(vertex, _)| vertex)
331            .collect();
332        let active_count = active_vertices.len();
333
334        if active_count == 0 || k >= active_count {
335            let mut source_cover = vec![0; num_source_vertices];
336            for vertex in active_vertices {
337                source_cover[vertex] = 1;
338            }
339            return ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
340                target: HamiltonianCircuit::new(SimpleGraph::cycle(3)),
341                construction: ConstructionKind::FixedYes { source_cover },
342            };
343        }
344
345        if k == 0 {
346            return ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
347                target: HamiltonianCircuit::new(SimpleGraph::path(3)),
348                construction: ConstructionKind::FixedNo {
349                    num_source_vertices,
350                },
351            };
352        }
353
354        let construction = TheoremConstruction {
355            num_source_vertices,
356            selector_count: k,
357            edges,
358            incident_edges,
359        };
360
361        let mut target_edges = BTreeSet::new();
362        for (edge_idx, &(u, v)) in construction.edges.iter().enumerate() {
363            for position in 1..6 {
364                insert_edge(
365                    &mut target_edges,
366                    construction.gadget_vertex(edge_idx, u, position),
367                    construction.gadget_vertex(edge_idx, u, position + 1),
368                );
369                insert_edge(
370                    &mut target_edges,
371                    construction.gadget_vertex(edge_idx, v, position),
372                    construction.gadget_vertex(edge_idx, v, position + 1),
373                );
374            }
375
376            insert_edge(
377                &mut target_edges,
378                construction.gadget_vertex(edge_idx, u, 3),
379                construction.gadget_vertex(edge_idx, v, 1),
380            );
381            insert_edge(
382                &mut target_edges,
383                construction.gadget_vertex(edge_idx, v, 3),
384                construction.gadget_vertex(edge_idx, u, 1),
385            );
386            insert_edge(
387                &mut target_edges,
388                construction.gadget_vertex(edge_idx, u, 6),
389                construction.gadget_vertex(edge_idx, v, 4),
390            );
391            insert_edge(
392                &mut target_edges,
393                construction.gadget_vertex(edge_idx, v, 6),
394                construction.gadget_vertex(edge_idx, u, 4),
395            );
396        }
397
398        for vertex in construction.active_vertices() {
399            let incident = &construction.incident_edges[vertex];
400            for window in incident.windows(2) {
401                insert_edge(
402                    &mut target_edges,
403                    construction.gadget_vertex(window[0], vertex, 6),
404                    construction.gadget_vertex(window[1], vertex, 1),
405                );
406            }
407
408            let (start, end) = construction
409                .path_endpoints(vertex)
410                .expect("active vertices have path endpoints");
411            for selector in 0..construction.selector_count {
412                insert_edge(&mut target_edges, selector, start);
413                insert_edge(&mut target_edges, selector, end);
414            }
415        }
416
417        let target = HamiltonianCircuit::new(SimpleGraph::new(
418            construction.selector_count + 12 * construction.edges.len(),
419            target_edges.into_iter().collect(),
420        ));
421
422        ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
423            target,
424            construction: ConstructionKind::Theorem(construction),
425        }
426    }
427}
428
429#[cfg(feature = "example-db")]
430pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
431    use crate::example_db::specs::assemble_rule_example;
432    use crate::export::SolutionPair;
433
434    vec![crate::example_db::specs::RuleExampleSpec {
435        id: "decisionminimumvertexcover_to_hamiltoniancircuit",
436        build: || {
437            let source = Decision::new(
438                MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1, 1, 1]),
439                1,
440            );
441            let source_config = vec![0, 1, 0];
442            let reduction = ReduceTo::<HamiltonianCircuit<SimpleGraph>>::reduce_to(&source);
443            let target_config = reduction.build_target_witness(&source_config);
444            assemble_rule_example(
445                &source,
446                reduction.target_problem(),
447                vec![SolutionPair {
448                    source_config,
449                    target_config,
450                }],
451            )
452        },
453    }]
454}
455
456#[cfg(test)]
457#[path = "../unit_tests/rules/decisionminimumvertexcover_hamiltoniancircuit.rs"]
458mod tests;