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, One>>` 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::types::One;
12use std::collections::BTreeSet;
13
14#[derive(Debug, Clone)]
15enum ConstructionKind {
16    FixedYes { source_cover: Vec<bool> },
17    FixedNo,
18    Theorem(TheoremConstruction),
19}
20
21#[derive(Debug, Clone)]
22struct TheoremConstruction {
23    forced_cover: Vec<bool>,
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: &[bool]) -> bool {
69        self.edges
70            .iter()
71            .all(|&(u, v)| selected.get(u) == Some(&true) || selected.get(v) == Some(&true))
72    }
73
74    #[cfg(any(test, feature = "example-db"))]
75    fn exact_selected_vertices(&self, source_cover: &[bool]) -> Option<Vec<usize>> {
76        if source_cover.len() != self.forced_cover.len() || !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])
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] {
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: &[bool]) -> 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 decode_solution(
186        &self,
187        target_solution: &[usize],
188    ) -> crate::rules::ExtractionResult<Vec<bool>> {
189        Ok({
190            let mut source_cover = self.forced_cover.clone();
191
192            let mut positions = vec![usize::MAX; target_solution.len()];
193            for (idx, &vertex) in target_solution.iter().enumerate() {
194                positions[vertex] = idx;
195            }
196
197            let len = target_solution.len();
198            let touches_selector = |vertex: usize| {
199                let idx = positions[vertex];
200                let prev = target_solution[(idx + len - 1) % len];
201                let next = target_solution[(idx + 1) % len];
202                prev < self.selector_count || next < self.selector_count
203            };
204
205            for vertex in self.active_vertices() {
206                let Some((start, end)) = self.path_endpoints(vertex) else {
207                    continue;
208                };
209                if touches_selector(start) && touches_selector(end) {
210                    source_cover[vertex] = true;
211                }
212            }
213
214            let selected_count = self.active_vertices().filter(|&v| source_cover[v]).count();
215            if selected_count != self.selector_count || !self.covers_all_edges(&source_cover) {
216                return Err(crate::rules::ExtractionError::invalid(
217                    "target circuit does not encode a source vertex cover of the required size",
218                ));
219            }
220
221            source_cover
222        })
223    }
224}
225
226/// Result of reducing Decision<MinimumVertexCover<SimpleGraph, One>> to
227/// HamiltonianCircuit<SimpleGraph>.
228#[derive(Debug, Clone)]
229pub struct ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
230    target: HamiltonianCircuit<SimpleGraph>,
231    construction: ConstructionKind,
232}
233
234impl ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
235    #[cfg(any(test, feature = "example-db"))]
236    fn build_target_witness(&self, source_cover: &[bool]) -> Vec<usize> {
237        match &self.construction {
238            ConstructionKind::FixedYes { .. } => vec![0, 1, 2],
239            ConstructionKind::FixedNo => Vec::new(),
240            ConstructionKind::Theorem(construction) => {
241                construction.build_target_witness(source_cover)
242            }
243        }
244    }
245}
246
247impl ReductionResult for ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
248    type Source = Decision<MinimumVertexCover<SimpleGraph, One>>;
249    type Target = HamiltonianCircuit<SimpleGraph>;
250
251    fn target_problem(&self) -> &Self::Target {
252        &self.target
253    }
254
255    fn extract_solution(
256        &self,
257        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
258    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
259        crate::rules::traits::validate_target_witness(
260            self.target_problem(),
261            target_solution,
262            |value| value.0,
263            "target configuration is not a Hamiltonian circuit",
264        )?;
265
266        Ok({
267            match &self.construction {
268                ConstructionKind::FixedYes { source_cover } => source_cover.clone(),
269                ConstructionKind::FixedNo => {
270                    return Err(crate::rules::ExtractionError::invalid(
271                        "the fixed negative target instance has no extractable witness",
272                    ))
273                }
274                ConstructionKind::Theorem(construction) => {
275                    construction.decode_solution(target_solution)?
276                }
277            }
278        })
279    }
280}
281
282fn normalize_edges(edges: Vec<(usize, usize)>) -> Vec<(usize, usize)> {
283    let mut normalized: Vec<_> = edges
284        .into_iter()
285        .map(|(u, v)| if u < v { (u, v) } else { (v, u) })
286        .collect();
287    normalized.sort_unstable();
288    normalized.dedup();
289    normalized
290}
291
292fn insert_edge(edges: &mut BTreeSet<(usize, usize)>, a: usize, b: usize) {
293    let edge = if a < b { (a, b) } else { (b, a) };
294    edges.insert(edge);
295}
296
297#[crate::aggregate_reduction(identity)]
298impl crate::rules::AggregateReductionResult
299    for ReductionDecisionMinimumVertexCoverToHamiltonianCircuit
300{
301}
302
303// Normalized surviving edges m'<=m contribute 14m' gadget edges;
304// incident chains add <=2m', selectors <=2n^2. Set deduplication only
305// decreases these counts; fixed YES/NO targets have at most three edges.
306#[reduction(transform = upper_bound {
307    num_vertices = "num_vertices + 12 * num_edges + 3",
308    num_edges = "16 * num_edges + 2 * num_vertices^2 + 3",
309})]
310impl ReduceTo<HamiltonianCircuit<SimpleGraph>> for Decision<MinimumVertexCover<SimpleGraph, One>> {
311    type Result = ReductionDecisionMinimumVertexCoverToHamiltonianCircuit;
312
313    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
314        let num_source_vertices = self.inner().graph().num_vertices();
315        // A loop forces its vertex into every cover. Reduce the remaining
316        // loopless graph with the budget left after selecting those vertices.
317        let mut forced_cover = vec![false; num_source_vertices];
318        let mut edges = normalize_edges(self.inner().graph().edges());
319        for &(u, v) in &edges {
320            if u == v {
321                forced_cover[u] = true;
322            }
323        }
324        let raw_bound = i128::from(*self.bound())
325            - forced_cover.iter().filter(|&&selected| selected).count() as i128;
326        edges.retain(|&(u, v)| !forced_cover[u] && !forced_cover[v]);
327        if raw_bound < 0 {
328            return Ok(ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
329                target: HamiltonianCircuit::new(SimpleGraph::path(3)),
330                construction: ConstructionKind::FixedNo,
331            });
332        }
333
334        let mut incident_edges = vec![Vec::new(); num_source_vertices];
335        for (edge_idx, &(u, v)) in edges.iter().enumerate() {
336            incident_edges[u].push(edge_idx);
337            incident_edges[v].push(edge_idx);
338        }
339
340        let active_vertices: Vec<_> = incident_edges
341            .iter()
342            .enumerate()
343            .filter(|(_, incident)| !incident.is_empty())
344            .map(|(vertex, _)| vertex)
345            .collect();
346        let active_count = active_vertices.len();
347
348        if raw_bound >= active_count as i128 {
349            let mut source_cover = forced_cover;
350            for vertex in active_vertices {
351                source_cover[vertex] = true;
352            }
353            return Ok(ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
354                target: HamiltonianCircuit::new(SimpleGraph::cycle(3)),
355                construction: ConstructionKind::FixedYes { source_cover },
356            });
357        }
358
359        if raw_bound == 0 {
360            return Ok(ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
361                target: HamiltonianCircuit::new(SimpleGraph::path(3)),
362                construction: ConstructionKind::FixedNo,
363            });
364        }
365
366        let construction = TheoremConstruction {
367            forced_cover,
368            selector_count: usize::try_from(raw_bound)
369                .expect("nonnegative bound is smaller than the active vertex count"),
370            edges,
371            incident_edges,
372        };
373
374        let mut target_edges = BTreeSet::new();
375        for (edge_idx, &(u, v)) in construction.edges.iter().enumerate() {
376            for position in 1..6 {
377                insert_edge(
378                    &mut target_edges,
379                    construction.gadget_vertex(edge_idx, u, position),
380                    construction.gadget_vertex(edge_idx, u, position + 1),
381                );
382                insert_edge(
383                    &mut target_edges,
384                    construction.gadget_vertex(edge_idx, v, position),
385                    construction.gadget_vertex(edge_idx, v, position + 1),
386                );
387            }
388
389            insert_edge(
390                &mut target_edges,
391                construction.gadget_vertex(edge_idx, u, 3),
392                construction.gadget_vertex(edge_idx, v, 1),
393            );
394            insert_edge(
395                &mut target_edges,
396                construction.gadget_vertex(edge_idx, v, 3),
397                construction.gadget_vertex(edge_idx, u, 1),
398            );
399            insert_edge(
400                &mut target_edges,
401                construction.gadget_vertex(edge_idx, u, 6),
402                construction.gadget_vertex(edge_idx, v, 4),
403            );
404            insert_edge(
405                &mut target_edges,
406                construction.gadget_vertex(edge_idx, v, 6),
407                construction.gadget_vertex(edge_idx, u, 4),
408            );
409        }
410
411        for vertex in construction.active_vertices() {
412            let incident = &construction.incident_edges[vertex];
413            for window in incident.windows(2) {
414                insert_edge(
415                    &mut target_edges,
416                    construction.gadget_vertex(window[0], vertex, 6),
417                    construction.gadget_vertex(window[1], vertex, 1),
418                );
419            }
420
421            let (start, end) = construction.path_endpoints(vertex).ok_or_else(|| {
422                crate::rules::ReductionError::invalid_target::<
423                    Decision<MinimumVertexCover<SimpleGraph, One>>,
424                    HamiltonianCircuit<SimpleGraph>,
425                >("active source vertex has no Hamiltonian gadget path endpoints")
426            })?;
427            for selector in 0..construction.selector_count {
428                insert_edge(&mut target_edges, selector, start);
429                insert_edge(&mut target_edges, selector, end);
430            }
431        }
432
433        let target = HamiltonianCircuit::new(SimpleGraph::new(
434            construction.selector_count + 12 * construction.edges.len(),
435            target_edges.into_iter().collect(),
436        ));
437
438        Ok(ReductionDecisionMinimumVertexCoverToHamiltonianCircuit {
439            target,
440            construction: ConstructionKind::Theorem(construction),
441        })
442    }
443}
444
445#[cfg(feature = "example-db")]
446pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
447    use crate::example_db::specs::assemble_rule_example;
448    use crate::export::SolutionPair;
449
450    vec![crate::example_db::specs::RuleExampleSpec {
451        id: "decisionminimumvertexcover_to_hamiltoniancircuit",
452        build: || {
453            let source = Decision::new(
454                MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![One; 3]),
455                1,
456            );
457            let source_config = vec![false, true, false];
458            let reduction = ReduceTo::<HamiltonianCircuit<SimpleGraph>>::reduce_to(&source)
459                .expect("reduction should succeed");
460            let target_config = reduction.build_target_witness(&source_config);
461            assemble_rule_example(
462                &source,
463                reduction.target_problem(),
464                vec![SolutionPair {
465                    source_config: serde_json::to_value(source_config)
466                        .expect("solution serialization must succeed"),
467                    target_config: serde_json::to_value(target_config)
468                        .expect("solution serialization must succeed"),
469                }],
470            )
471        },
472    }]
473}
474
475#[cfg(test)]
476#[path = "../unit_tests/rules/decisionminimumvertexcover_hamiltoniancircuit.rs"]
477mod tests;