Skip to main content

problemreductions/rules/
minimumvertexcover_ensemblecomputation.rs

1//! Reduction from MinimumVertexCover (unit-weight) to EnsembleComputation.
2//!
3//! Given a graph G = (V, E), construct an EnsembleComputation instance where:
4//! - Universe A = V ∪ {a₀} (fresh element a₀ at index |V|)
5//! - Collection C = {{a₀, u, v} : {u,v} ∈ E}
6//! - Budget = |V| + |E| (search space bound; the optimal value encodes K*)
7//!
8//! The minimum sequence length is K* + |E|, where K* is the minimum vertex
9//! cover size. This follows from the Garey & Johnson proof (PO9): each cover
10//! vertex contributes one {a₀} ∪ {v} operation, and each edge contributes
11//! one {u} ∪ z_k operation.
12//!
13//! Reference: Garey & Johnson, *Computers and Intractability*, Appendix Problem PO9.
14
15use crate::models::graph::MinimumVertexCover;
16use crate::models::misc::EnsembleComputation;
17use crate::reduction;
18use crate::rules::traits::{ReduceTo, ReductionResult};
19use crate::topology::{Graph, SimpleGraph};
20use crate::types::One;
21
22/// Result of reducing MinimumVertexCover to EnsembleComputation.
23#[derive(Debug, Clone)]
24pub struct ReductionVCToEC {
25    target: EnsembleComputation,
26    /// Number of vertices in the source graph (= index of fresh element a₀).
27    num_vertices: usize,
28}
29
30impl ReductionResult for ReductionVCToEC {
31    type Source = MinimumVertexCover<SimpleGraph, One>;
32    type Target = EnsembleComputation;
33
34    fn target_problem(&self) -> &Self::Target {
35        &self.target
36    }
37
38    /// Extract a vertex cover from an EnsembleComputation witness.
39    ///
40    /// The GJ proof shows that any minimum-length sequence can be normalized
41    /// so that only two forms of operations appear:
42    /// - z_i = {a₀} ∪ {v}  — vertex v is in the cover
43    /// - z_j = {u} ∪ z_k   — edge {u, v_r} is covered by v_r
44    ///
45    /// We collect all vertices that appear as singleton operands (index < |V|)
46    /// in the meaningful steps only (before all required subsets are covered).
47    /// Padding steps beyond the coverage point are ignored.
48    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
49        use crate::traits::Problem;
50        use crate::types::Min;
51
52        let meaningful_steps = match self.target.evaluate(target_solution) {
53            Min(Some(n)) => n,
54            _ => return vec![0; self.num_vertices],
55        };
56        let mut cover = vec![0usize; self.num_vertices];
57
58        for step in 0..meaningful_steps {
59            let left = target_solution[2 * step];
60            let right = target_solution[2 * step + 1];
61
62            if left < self.num_vertices {
63                cover[left] = 1;
64            }
65            if right < self.num_vertices {
66                cover[right] = 1;
67            }
68        }
69
70        cover
71    }
72}
73
74#[reduction(
75    overhead = {
76        universe_size = "num_vertices + 1",
77        num_subsets = "num_edges",
78    }
79)]
80impl ReduceTo<EnsembleComputation> for MinimumVertexCover<SimpleGraph, One> {
81    type Result = ReductionVCToEC;
82
83    fn reduce_to(&self) -> Self::Result {
84        let num_vertices = self.graph().num_vertices();
85        let edges = self.graph().edges();
86        let num_edges = edges.len();
87        let a0 = num_vertices; // fresh element index
88
89        // Universe A = V ∪ {a₀}, size = |V| + 1
90        let universe_size = num_vertices + 1;
91
92        // Collection C: for each edge {u, v}, add subset {a₀, u, v}
93        let subsets: Vec<Vec<usize>> = edges.iter().map(|&(u, v)| vec![a0, u, v]).collect();
94
95        // Budget bounds the search space; the optimal sequence length
96        // is K* + |E| where K* is the minimum vertex cover size.
97        let budget = num_vertices + num_edges;
98
99        let target = EnsembleComputation::new(universe_size, subsets, budget);
100
101        ReductionVCToEC {
102            target,
103            num_vertices,
104        }
105    }
106}
107
108#[cfg(feature = "example-db")]
109pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
110    use crate::export::SolutionPair;
111
112    vec![crate::example_db::specs::RuleExampleSpec {
113        id: "minimumvertexcover_to_ensemblecomputation",
114        build: || {
115            // Single edge graph: 2 vertices {0,1}, 1 edge (0,1)
116            // Minimum vertex cover K* = 1 (either {0} or {1})
117            // Budget = 2 + 1 = 3, universe_size = 3, a₀ = 2
118            // Subsets = {{0,1,2}}
119            // Optimal sequence length = K* + |E| = 1 + 1 = 2
120            let source = MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![One; 2]);
121
122            // Optimal sequence for cover {0} (2 steps):
123            // Step 0: {a₀=2} ∪ {0} → z₀ = {0,2}   operands: (2, 0)
124            // Step 1: {1} ∪ z₀ → z₁ = {0,1,2} ✓    operands: (1, 3) where 3 = universe_size + 0
125            // Step 2: padding (unused)                operands: (2, 1)
126            let target_config = vec![
127                2, 0, // step 0: {a₀} ∪ {0}
128                1, 3, // step 1: {1} ∪ z₀
129                2, 1, // step 2: padding
130            ];
131            // Extraction picks up vertices 0 (step 0) and 1 (step 1) from the
132            // 2 meaningful steps. Step 2 is padding and is ignored.
133            // Cover {0,1} is valid (though not minimum — the optimal witness
134            // is found by BruteForce, giving cover {0} or {1}).
135            let source_config = vec![1, 1];
136
137            crate::example_db::specs::rule_example_with_witness::<_, EnsembleComputation>(
138                source,
139                SolutionPair {
140                    source_config,
141                    target_config,
142                },
143            )
144        },
145    }]
146}
147
148#[cfg(test)]
149#[path = "../unit_tests/rules/minimumvertexcover_ensemblecomputation.rs"]
150mod tests;