Skip to main content

problemreductions/models/graph/
minimum_vertex_cover.rs

1//! Vertex Covering problem implementation.
2//!
3//! The Vertex Cover problem asks for a minimum weight subset of vertices
4//! such that every edge has at least one endpoint in the subset.
5
6use crate::models::decision::Decision;
7use crate::registry::{CreateSpec, FieldInfo, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::{Min, One, WeightElement};
11use num_traits::Zero;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "MinimumVertexCover",
17        display_name: "Minimum Vertex Cover",
18        aliases: &["MVC"],
19        dimensions: &[
20            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21            VariantDimension::new("weight", "i64", &["i64", "One"]),
22        ],
23        category: crate::registry::ProblemCategory::Graph,
24        module_path: module_path!(),
25        description: "Find minimum weight vertex cover in a graph",
26        fields: MinimumVertexCoverCreateSpec::<i64>::FIELDS,
27    }
28}
29
30/// The Vertex Covering problem.
31///
32/// Given a graph G = (V, E) and weights w_v for each vertex,
33/// find a subset S ⊆ V such that:
34/// - Every edge has at least one endpoint in S (covering constraint)
35/// - The total weight Σ_{v ∈ S} w_v is minimized
36///
37/// # Example
38///
39/// ```
40/// use problemreductions::models::graph::MinimumVertexCover;
41/// use problemreductions::topology::SimpleGraph;
42/// use problemreductions::{Problem, BruteForce};
43///
44/// // Create a path graph 0-1-2
45/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]);
46/// let problem = MinimumVertexCover::new(graph, vec![1; 3]);
47///
48/// // Solve with brute force
49/// let solver = BruteForce::new();
50/// let solutions = solver.find_all_witnesses(&problem).unwrap();
51///
52/// // Minimum vertex cover is just vertex 1
53/// assert!(solutions.contains(&vec![false, true, false]));
54/// ```
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MinimumVertexCover<G, W> {
57    /// The underlying graph.
58    graph: G,
59    /// Weights for each vertex.
60    weights: Vec<W>,
61}
62
63#[derive(Debug, Deserialize, crate::CreateSpec)]
64struct MinimumVertexCoverCreateSpec<W> {
65    /// The underlying graph G=(V,E).
66    graph: SimpleGraph,
67    /// Vertex weights w: V -> R.
68    weights: Option<Vec<W>>,
69}
70
71impl<W: WeightElement> TryFrom<MinimumVertexCoverCreateSpec<W>>
72    for MinimumVertexCover<SimpleGraph, W>
73{
74    type Error = crate::registry::ConstructionError;
75    fn try_from(spec: MinimumVertexCoverCreateSpec<W>) -> Result<Self, Self::Error> {
76        let weights = spec
77            .weights
78            .unwrap_or_else(|| vec![W::unit(); spec.graph.num_vertices()]);
79        if weights.len() != spec.graph.num_vertices() {
80            return Err(format!(
81                "weights has {} entries, expected {}",
82                weights.len(),
83                spec.graph.num_vertices()
84            )
85            .into());
86        }
87        Ok(Self::new(spec.graph, weights))
88    }
89}
90
91impl<G: Graph, W: Clone + Default> MinimumVertexCover<G, W> {
92    /// Create a Vertex Covering problem from a graph with given weights.
93    pub fn new(graph: G, weights: Vec<W>) -> Self {
94        assert_eq!(
95            weights.len(),
96            graph.num_vertices(),
97            "weights length must match graph num_vertices"
98        );
99        Self { graph, weights }
100    }
101
102    /// Get a reference to the underlying graph.
103    pub fn graph(&self) -> &G {
104        &self.graph
105    }
106
107    /// Get a reference to the weights.
108    pub fn weights(&self) -> &[W] {
109        &self.weights
110    }
111
112    /// Check if the problem uses a non-unit weight type.
113    pub fn is_weighted(&self) -> bool
114    where
115        W: WeightElement,
116    {
117        !W::IS_UNIT
118    }
119
120    /// Check if a configuration is a valid vertex cover.
121    pub fn is_valid_solution(&self, config: &[bool]) -> bool {
122        is_vertex_cover_config(&self.graph, config)
123    }
124}
125
126impl<G: Graph, W: WeightElement> MinimumVertexCover<G, W> {
127    /// Get the number of vertices in the underlying graph.
128    pub fn num_vertices(&self) -> usize {
129        self.graph().num_vertices()
130    }
131
132    /// Get the number of edges in the underlying graph.
133    pub fn num_edges(&self) -> usize {
134        self.graph().num_edges()
135    }
136}
137
138impl<G, W> Problem for MinimumVertexCover<G, W>
139where
140    G: Graph + crate::variant::VariantParam,
141    W: WeightElement + crate::variant::VariantParam,
142{
143    const NAME: &'static str = "MinimumVertexCover";
144    type Solution = Vec<bool>;
145    type Value = Min<W::Sum>;
146
147    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
148
149    fn variant() -> Vec<(&'static str, &'static str)> {
150        crate::variant_params![G, W]
151    }
152
153    fn evaluate(
154        &self,
155        config: &Self::Solution,
156    ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
157        Ok({
158            if config.len() != self.graph.num_vertices() {
159                return Err(crate::traits::EvaluationError::InvalidConfiguration(
160                    "vertex-selection length does not match the graph".into(),
161                ));
162            }
163            if !is_vertex_cover_config(&self.graph, config) {
164                return Ok(Min(None));
165            }
166            let mut total = W::Sum::zero();
167            for (i, &selected) in config.iter().enumerate() {
168                if selected {
169                    total = W::checked_add_to_sum(
170                        total,
171                        self.weights[i].to_sum(),
172                        "summing selected vertex-cover weights",
173                    )?;
174                }
175            }
176            Min(Some(total))
177        })
178    }
179}
180
181impl<G, W> crate::solvers::BruteForceProblem for MinimumVertexCover<G, W>
182where
183    G: Graph + crate::variant::VariantParam,
184    W: WeightElement + crate::variant::VariantParam,
185{
186    fn dimensions(&self) -> Vec<usize> {
187        vec![2; self.graph.num_vertices()]
188    }
189}
190
191/// Check if a configuration forms a valid vertex cover.
192pub(crate) fn is_vertex_cover_config<G: Graph>(graph: &G, config: &[bool]) -> bool {
193    for (u, v) in graph.edges() {
194        let u_covered = config.get(u).copied().unwrap_or(false);
195        let v_covered = config.get(v).copied().unwrap_or(false);
196        if !u_covered && !v_covered {
197            return false;
198        }
199    }
200    true
201}
202
203crate::impl_random_generate!(MinimumVertexCover<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
204    Ok(MinimumVertexCover::new(spec.graph()?, vec![1; spec.num_vertices]))
205});
206crate::impl_random_generate!(MinimumVertexCover<SimpleGraph, One>, crate::random::SimpleGraphRandomSpec, |spec| {
207    Ok(MinimumVertexCover::new(spec.graph()?, vec![One; spec.num_vertices]))
208});
209
210#[derive(Debug, Deserialize, crate::CreateSpec)]
211struct MinimumVertexCoverOneCreateSpec {
212    /// The underlying graph.
213    graph: SimpleGraph,
214}
215
216impl TryFrom<MinimumVertexCoverOneCreateSpec> for MinimumVertexCover<SimpleGraph, One> {
217    type Error = crate::registry::ConstructionError;
218    fn try_from(spec: MinimumVertexCoverOneCreateSpec) -> Result<Self, Self::Error> {
219        let weights = vec![One; spec.graph.num_vertices()];
220        Ok(Self::new(spec.graph, weights))
221    }
222}
223
224crate::declare_variants! {
225    default MinimumVertexCover<SimpleGraph, i64> => "1.1996^num_vertices" create MinimumVertexCoverCreateSpec<i64> random,
226    MinimumVertexCover<SimpleGraph, One> => "1.1996^num_vertices" create MinimumVertexCoverOneCreateSpec random,
227}
228
229crate::register_brute_force! {
230    MinimumVertexCover<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
231    MinimumVertexCover<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
232}
233
234impl<G, W> crate::models::decision::DecisionProblemMeta for MinimumVertexCover<G, W>
235where
236    G: Graph + crate::variant::VariantParam,
237    W: WeightElement + crate::variant::VariantParam,
238    W::Sum: std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned,
239{
240    const DECISION_NAME: &'static str = "DecisionMinimumVertexCover";
241}
242
243impl Decision<MinimumVertexCover<SimpleGraph, i64>> {
244    /// Number of vertices in the underlying graph.
245    pub fn num_vertices(&self) -> usize {
246        self.inner().num_vertices()
247    }
248
249    /// Number of edges in the underlying graph.
250    pub fn num_edges(&self) -> usize {
251        self.inner().num_edges()
252    }
253
254    /// Decision bound as a nonnegative integer.
255    pub fn k(&self) -> usize {
256        (*self.bound()).try_into().unwrap_or(0)
257    }
258}
259
260#[derive(Debug, Deserialize, crate::CreateSpec)]
261struct DecisionMinimumVertexCoverRandomSpec {
262    /// Number of graph vertices.
263    num_vertices: usize,
264    /// Independent edge probability (default: 0.5).
265    edge_prob: Option<f64>,
266    /// Seed for reproducible generation.
267    seed: Option<i64>,
268    /// Maximum allowed cover cost.
269    bound: i64,
270}
271
272crate::impl_random_generate!(
273    Decision<MinimumVertexCover<SimpleGraph, i64>>,
274    DecisionMinimumVertexCoverRandomSpec,
275    |spec| {
276        if spec.bound < 0 {
277            return Err("bound must be nonnegative".to_string().into());
278        }
279        let graph = crate::random::SimpleGraphRandomSpec {
280            num_vertices: spec.num_vertices,
281            edge_prob: spec.edge_prob,
282            seed: spec.seed,
283        }
284        .graph()?;
285        Ok(Decision::new(
286            MinimumVertexCover::new(graph, vec![1; spec.num_vertices]),
287            spec.bound,
288        ))
289    }
290);
291
292crate::register_decision_variant!(
293    MinimumVertexCover<SimpleGraph, i64>,
294    "DecisionMinimumVertexCover",
295    "1.1996^num_vertices",
296    &["DMVC", "VC", "VertexCover"],
297    "Decision version: does a vertex cover of cost <= bound exist?",
298    category: crate::registry::ProblemCategory::Graph,
299    dims: [
300        VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
301        VariantDimension::new("weight", "i64", &["i64"]),
302    ],
303    fields: [
304        FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
305        FieldInfo { name: "weights", type_name: "Vec<W>", description: "Vertex weights w: V -> R" },
306        FieldInfo { name: "bound", type_name: "W::Sum", description: "Decision bound (maximum allowed cover cost)" },
307    ],
308    decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
309    random
310);
311
312#[cfg(feature = "example-db")]
313pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
314    vec![crate::example_db::specs::ModelExampleSpec {
315        id: "minimum_vertex_cover_simplegraph",
316        instance: Box::new(MinimumVertexCover::new(
317            SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
318            vec![1i64; 5],
319        )),
320        optimal_config: serde_json::json!(vec![true, false, false, true, true]),
321        optimal_value: serde_json::json!(3),
322    }]
323}
324
325#[cfg(feature = "example-db")]
326pub(crate) fn decision_canonical_model_example_specs(
327) -> Vec<crate::example_db::specs::ModelExampleSpec> {
328    vec![crate::example_db::specs::ModelExampleSpec {
329        id: "decision_minimum_vertex_cover_simplegraph",
330        instance: Box::new(crate::models::decision::Decision::new(
331            MinimumVertexCover::new(
332                SimpleGraph::new(4, vec![(0, 1), (1, 2), (0, 2), (2, 3)]),
333                vec![1i64; 4],
334            ),
335            2,
336        )),
337        optimal_config: serde_json::json!(vec![true, false, true, false]),
338        optimal_value: serde_json::json!(true),
339    }]
340}
341
342#[cfg(feature = "example-db")]
343pub(crate) fn decision_canonical_rule_example_specs(
344) -> Vec<crate::example_db::specs::RuleExampleSpec> {
345    vec![crate::example_db::specs::RuleExampleSpec {
346        id: "decision_minimum_vertex_cover_to_minimum_vertex_cover",
347        build: || {
348            use crate::example_db::specs::assemble_rule_example;
349            use crate::export::SolutionPair;
350            use crate::rules::{AggregateReductionResult, ReduceToAggregate};
351
352            let source = crate::models::decision::Decision::new(
353                MinimumVertexCover::new(
354                    SimpleGraph::new(4, vec![(0, 1), (1, 2), (0, 2), (2, 3)]),
355                    vec![1i64; 4],
356                ),
357                2,
358            );
359            let result = source
360                .reduce_to_aggregate()
361                .expect("reduction should succeed");
362            let target = result.target_problem();
363            let config = vec![true, false, true, false];
364            assemble_rule_example(
365                &source,
366                target,
367                vec![SolutionPair {
368                    source_config: serde_json::json!(config.clone()),
369                    target_config: serde_json::json!(config),
370                }],
371            )
372        },
373    }]
374}
375
376/// Check if a set of vertices forms a vertex cover.
377///
378/// # Arguments
379/// * `graph` - The graph
380/// * `selected` - Boolean slice indicating which vertices are selected
381///
382/// # Panics
383/// Panics if `selected.len() != graph.num_vertices()`.
384#[cfg(test)]
385pub(crate) fn is_vertex_cover<G: Graph>(graph: &G, selected: &[bool]) -> bool {
386    assert_eq!(
387        selected.len(),
388        graph.num_vertices(),
389        "selected length must match num_vertices"
390    );
391    for (u, v) in graph.edges() {
392        if !selected[u] && !selected[v] {
393            return false;
394        }
395    }
396    true
397}
398
399#[cfg(test)]
400#[path = "../../unit_tests/models/graph/minimum_vertex_cover.rs"]
401mod tests;