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 = max(1, |V| + |E|) (positive search-space bound)
7//!
8//! For loopless simple graphs, 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*, Theorem 3.6, pp. 66–68 (also Appendix 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 one vertex per pair-producing operation in the evaluated prefix.
39    /// Every required triple uses an earlier pair. The chosen endpoint covers
40    /// its edge. An L-step program yields at most L minus the number of
41    /// distinct required triples; loops instead require endpoint pairs.
42    /// This applies to arbitrary programs, without a normal-form assumption.
43    fn extract_solution(
44        &self,
45        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
46    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
47        let value =
48            crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
49        let crate::types::Min(Some(length)) = value else {
50            return Err(crate::rules::ExtractionError::invalid(
51                "target configuration does not encode a valid ensemble computation",
52            ));
53        };
54        let meaningful_steps = usize::try_from(length).map_err(|_| {
55            crate::rules::ExtractionError::invalid(
56                "ensemble operation count cannot be represented as usize",
57            )
58        })?;
59        let mut cover = vec![false; self.num_vertices];
60        let universe_size = self.target.universe_size();
61        for &[left, right] in target_solution
62            .as_chunks::<2>()
63            .0
64            .iter()
65            .take(meaningful_steps)
66        {
67            if left < universe_size && right < universe_size {
68                // Only two singleton operands can produce a two-element set.
69                // The fresh atom is largest, so min selects the original
70                // vertex in {a0,v}, or an endpoint of an edge-pair {u,v}.
71                cover[left.min(right)] = true;
72            }
73        }
74        Ok(cover)
75    }
76}
77
78#[reduction(
79    transform = upper_bound {
80        universe_size = "num_vertices + 1",
81        num_subsets = "num_edges",
82        budget = "num_vertices + num_edges + 1",
83    }
84)]
85impl ReduceTo<EnsembleComputation> for MinimumVertexCover<SimpleGraph, One> {
86    type Result = ReductionVCToEC;
87
88    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
89        let num_vertices = self.graph().num_vertices();
90        let edges = self.graph().edges();
91        let num_edges = edges.len();
92        let a0 = num_vertices; // fresh element index
93
94        // Universe A = V ∪ {a₀}, size = |V| + 1
95        let overflow = || {
96            crate::rules::ReductionError::integer_overflow::<Self, EnsembleComputation>(
97                "computing ensemble universe, budget, or operand dimensions",
98            )
99        };
100        let universe_size = num_vertices.checked_add(1).ok_or_else(overflow)?;
101
102        // Collection C: for each edge {u, v}, add subset {a₀, u, v}
103        let subsets: Vec<Vec<usize>> = edges.iter().map(|&(u, v)| vec![a0, u, v]).collect();
104
105        // Budget bounds the search space; the optimal sequence length
106        // is K* + |E| where K* is the minimum vertex cover size.
107        let budget = num_vertices
108            .checked_add(num_edges)
109            .ok_or_else(overflow)?
110            .max(1);
111        universe_size.checked_add(budget).ok_or_else(overflow)?;
112        budget.checked_mul(2).ok_or_else(overflow)?;
113
114        let target = EnsembleComputation::try_new(universe_size, subsets, budget)
115            .map_err(crate::rules::ReductionError::construction::<Self, EnsembleComputation>)?;
116
117        Ok(ReductionVCToEC {
118            target,
119            num_vertices,
120        })
121    }
122}
123
124#[cfg(feature = "example-db")]
125pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
126    use crate::export::SolutionPair;
127
128    vec![crate::example_db::specs::RuleExampleSpec {
129        id: "minimumvertexcover_to_ensemblecomputation",
130        build: || {
131            // Single edge graph: 2 vertices {0,1}, 1 edge (0,1)
132            // Minimum vertex cover K* = 1 (either {0} or {1})
133            // Budget = 2 + 1 = 3, universe_size = 3, a₀ = 2
134            // Subsets = {{0,1,2}}
135            // Optimal sequence length = K* + |E| = 1 + 1 = 2
136            let source = MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![One; 2]);
137
138            // Optimal sequence for cover {0} (2 steps):
139            // Step 0: {a₀=2} ∪ {0} → z₀ = {0,2}   operands: (2, 0)
140            // Step 1: {1} ∪ z₀ → z₁ = {0,1,2} ✓    operands: (1, 3) where 3 = universe_size + 0
141            // Step 2: padding (unused)                operands: (2, 1)
142            let target_config = vec![
143                2, 0, // step 0: {a₀} ∪ {0}
144                1, 3, // step 1: {1} ∪ z₀
145                2, 1, // step 2: padding
146            ];
147            // Only step 0 produces a pair; step 1 produces the required
148            // triple, and step 2 is padding. Extraction returns minimum {0}.
149            let source_config = vec![true, false];
150
151            crate::example_db::specs::rule_example_with_witness::<_, EnsembleComputation>(
152                source,
153                SolutionPair {
154                    source_config: serde_json::to_value(source_config)
155                        .expect("solution serialization must succeed"),
156                    target_config: serde_json::to_value(target_config)
157                        .expect("solution serialization must succeed"),
158                },
159            )
160        },
161    }]
162}
163
164#[cfg(test)]
165#[path = "../unit_tests/rules/minimumvertexcover_ensemblecomputation.rs"]
166mod tests;