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)]
56#[serde(try_from = "MinimumVertexCoverData<G, W>")]
57#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: Clone + Default + Deserialize<'de>"))]
58pub struct MinimumVertexCover<G, W> {
59    /// The underlying graph.
60    graph: G,
61    /// Weights for each vertex.
62    weights: Vec<W>,
63}
64
65#[derive(Deserialize)]
66struct MinimumVertexCoverData<G, W> {
67    graph: G,
68    weights: Vec<W>,
69}
70
71impl<G, W> TryFrom<MinimumVertexCoverData<G, W>> for MinimumVertexCover<G, W>
72where
73    G: Graph,
74    W: Clone + Default,
75{
76    type Error = crate::registry::ConstructionError;
77
78    fn try_from(data: MinimumVertexCoverData<G, W>) -> Result<Self, Self::Error> {
79        Self::try_new(data.graph, data.weights)
80    }
81}
82
83#[derive(Debug, Deserialize, crate::CreateSpec)]
84struct MinimumVertexCoverCreateSpec<W> {
85    /// The underlying graph G=(V,E).
86    graph: SimpleGraph,
87    /// Vertex weights w: V -> R.
88    weights: Option<Vec<W>>,
89}
90
91impl<W: WeightElement> TryFrom<MinimumVertexCoverCreateSpec<W>>
92    for MinimumVertexCover<SimpleGraph, W>
93{
94    type Error = crate::registry::ConstructionError;
95    fn try_from(spec: MinimumVertexCoverCreateSpec<W>) -> Result<Self, Self::Error> {
96        let weights = spec
97            .weights
98            .unwrap_or_else(|| vec![W::unit(); spec.graph.num_vertices()]);
99        Self::try_new(spec.graph, weights)
100    }
101}
102
103impl<G: Graph, W: Clone + Default> MinimumVertexCover<G, W> {
104    /// Create a Vertex Covering problem from a graph with given weights.
105    pub fn new(graph: G, weights: Vec<W>) -> Self {
106        Self::try_new(graph, weights).unwrap_or_else(|error| panic!("{error}"))
107    }
108
109    fn try_new(graph: G, weights: Vec<W>) -> Result<Self, crate::registry::ConstructionError> {
110        if weights.len() != graph.num_vertices() {
111            return Err(crate::registry::ConstructionError::length_mismatch(
112                "weights",
113                weights.len(),
114                graph.num_vertices(),
115            ));
116        }
117        Ok(Self { graph, weights })
118    }
119
120    /// Get a reference to the underlying graph.
121    pub fn graph(&self) -> &G {
122        &self.graph
123    }
124
125    /// Get a reference to the weights.
126    pub fn weights(&self) -> &[W] {
127        &self.weights
128    }
129
130    /// Check if the problem uses a non-unit weight type.
131    pub fn is_weighted(&self) -> bool
132    where
133        W: WeightElement,
134    {
135        !W::IS_UNIT
136    }
137
138    /// Check if a configuration is a valid vertex cover.
139    pub fn is_valid_solution(&self, config: &[bool]) -> bool {
140        is_vertex_cover_config(&self.graph, config)
141    }
142}
143
144impl<G: Graph, W: WeightElement> MinimumVertexCover<G, W> {
145    /// Get the number of vertices in the underlying graph.
146    pub fn num_vertices(&self) -> usize {
147        self.graph().num_vertices()
148    }
149
150    /// Get the number of edges in the underlying graph.
151    pub fn num_edges(&self) -> usize {
152        self.graph().num_edges()
153    }
154}
155
156impl<G, W> Problem for MinimumVertexCover<G, W>
157where
158    G: Graph + crate::variant::VariantParam,
159    W: WeightElement + crate::variant::VariantParam,
160{
161    const NAME: &'static str = "MinimumVertexCover";
162    type Solution = Vec<bool>;
163    type Value = Min<W::Sum>;
164
165    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
166
167    fn variant() -> Vec<(&'static str, &'static str)> {
168        crate::variant_params![G, W]
169    }
170
171    fn evaluate(
172        &self,
173        config: &Self::Solution,
174    ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
175        Ok({
176            if config.len() != self.graph.num_vertices() {
177                return Err(crate::traits::EvaluationError::InvalidConfiguration(
178                    "vertex-selection length does not match the graph".into(),
179                ));
180            }
181            if !is_vertex_cover_config(&self.graph, config) {
182                return Ok(Min(None));
183            }
184            let mut total = W::Sum::zero();
185            for (i, &selected) in config.iter().enumerate() {
186                if selected {
187                    total = W::checked_add_to_sum(
188                        total,
189                        self.weights[i].to_sum(),
190                        "summing selected vertex-cover weights",
191                    )?;
192                }
193            }
194            Min(Some(total))
195        })
196    }
197}
198
199impl<G, W> crate::solvers::BruteForceProblem for MinimumVertexCover<G, W>
200where
201    G: Graph + crate::variant::VariantParam,
202    W: WeightElement + crate::variant::VariantParam,
203{
204    fn dimensions(&self) -> Vec<usize> {
205        vec![2; self.graph.num_vertices()]
206    }
207}
208
209/// Check if a configuration forms a valid vertex cover.
210pub(crate) fn is_vertex_cover_config<G: Graph>(graph: &G, config: &[bool]) -> bool {
211    for (u, v) in graph.edges() {
212        let u_covered = config.get(u).copied().unwrap_or(false);
213        let v_covered = config.get(v).copied().unwrap_or(false);
214        if !u_covered && !v_covered {
215            return false;
216        }
217    }
218    true
219}
220
221crate::impl_random_generate!(MinimumVertexCover<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
222    Ok(MinimumVertexCover::new(spec.graph()?, vec![1; spec.num_vertices]))
223});
224crate::impl_random_generate!(MinimumVertexCover<SimpleGraph, One>, crate::random::SimpleGraphRandomSpec, |spec| {
225    Ok(MinimumVertexCover::new(spec.graph()?, vec![One; spec.num_vertices]))
226});
227
228#[derive(Debug, Deserialize, crate::CreateSpec)]
229struct MinimumVertexCoverOneCreateSpec {
230    /// The underlying graph.
231    graph: SimpleGraph,
232}
233
234impl TryFrom<MinimumVertexCoverOneCreateSpec> for MinimumVertexCover<SimpleGraph, One> {
235    type Error = crate::registry::ConstructionError;
236    fn try_from(spec: MinimumVertexCoverOneCreateSpec) -> Result<Self, Self::Error> {
237        let weights = vec![One; spec.graph.num_vertices()];
238        Self::try_new(spec.graph, weights)
239    }
240}
241
242crate::declare_variants! {
243    default MinimumVertexCover<SimpleGraph, i64> => "1.1996^num_vertices" create MinimumVertexCoverCreateSpec<i64> random,
244    MinimumVertexCover<SimpleGraph, One> => "1.1996^num_vertices" create MinimumVertexCoverOneCreateSpec random,
245}
246
247crate::register_brute_force! {
248    MinimumVertexCover<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
249    MinimumVertexCover<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
250}
251
252impl<G, W> crate::models::decision::DecisionProblemMeta for MinimumVertexCover<G, W>
253where
254    G: Graph + crate::variant::VariantParam,
255    W: WeightElement + crate::variant::VariantParam,
256    W::Sum: std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned,
257{
258    const DECISION_NAME: &'static str = "DecisionMinimumVertexCover";
259}
260
261impl Decision<MinimumVertexCover<SimpleGraph, i64>> {
262    /// Number of vertices in the underlying graph.
263    pub fn num_vertices(&self) -> usize {
264        self.inner().num_vertices()
265    }
266
267    /// Number of edges in the underlying graph.
268    pub fn num_edges(&self) -> usize {
269        self.inner().num_edges()
270    }
271
272    /// Decision bound as a nonnegative integer.
273    pub fn k(&self) -> usize {
274        (*self.bound()).try_into().unwrap_or(0)
275    }
276}
277
278#[derive(Debug, Deserialize, crate::CreateSpec)]
279struct DecisionMinimumVertexCoverRandomSpec {
280    /// Number of graph vertices.
281    num_vertices: usize,
282    /// Independent edge probability (default: 0.5).
283    edge_prob: Option<f64>,
284    /// Seed for reproducible generation.
285    seed: Option<i64>,
286    /// Maximum allowed cover cost.
287    bound: i64,
288}
289
290crate::impl_random_generate!(
291    Decision<MinimumVertexCover<SimpleGraph, i64>>,
292    DecisionMinimumVertexCoverRandomSpec,
293    |spec| {
294        if spec.bound < 0 {
295            return Err("bound must be nonnegative".to_string().into());
296        }
297        let graph = crate::random::SimpleGraphRandomSpec {
298            num_vertices: spec.num_vertices,
299            edge_prob: spec.edge_prob,
300            seed: spec.seed,
301        }
302        .graph()?;
303        Ok(Decision::new(
304            MinimumVertexCover::new(graph, vec![1; spec.num_vertices]),
305            spec.bound,
306        ))
307    }
308);
309
310crate::register_decision_variant!(
311    MinimumVertexCover<SimpleGraph, i64>,
312    "DecisionMinimumVertexCover",
313    "1.1996^num_vertices",
314    &["DMVC", "VC", "VertexCover"],
315    "Decision version: does a vertex cover of cost <= bound exist?",
316    category: crate::registry::ProblemCategory::Graph,
317    dims: [
318        VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
319        VariantDimension::new("weight", "i64", &["i64", "One"]),
320    ],
321    fields: [
322        FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
323        FieldInfo { name: "weights", type_name: "Vec<W>", description: "Vertex weights w: V -> R" },
324        FieldInfo { name: "bound", type_name: "W::Sum", description: "Decision bound (maximum allowed cover cost)" },
325    ],
326    additional: [MinimumVertexCover<SimpleGraph, One> => "1.1996^num_vertices"],
327    decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
328    random
329);
330
331#[cfg(feature = "example-db")]
332pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
333    vec![crate::example_db::specs::ModelExampleSpec {
334        id: "minimum_vertex_cover_simplegraph",
335        instance: Box::new(MinimumVertexCover::new(
336            SimpleGraph::new(5, vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]),
337            vec![1i64; 5],
338        )),
339        optimal_config: serde_json::json!(vec![true, false, false, true, true]),
340        optimal_value: serde_json::json!(3),
341    }]
342}
343
344#[cfg(feature = "example-db")]
345pub(crate) fn decision_canonical_model_example_specs(
346) -> Vec<crate::example_db::specs::ModelExampleSpec> {
347    vec![crate::example_db::specs::ModelExampleSpec {
348        id: "decision_minimum_vertex_cover_simplegraph",
349        instance: Box::new(crate::models::decision::Decision::new(
350            MinimumVertexCover::new(
351                SimpleGraph::new(4, vec![(0, 1), (1, 2), (0, 2), (2, 3)]),
352                vec![1i64; 4],
353            ),
354            2,
355        )),
356        optimal_config: serde_json::json!(vec![true, false, true, false]),
357        optimal_value: serde_json::json!(true),
358    }]
359}
360
361#[cfg(feature = "example-db")]
362pub(crate) fn decision_canonical_rule_example_specs(
363) -> Vec<crate::example_db::specs::RuleExampleSpec> {
364    use crate::example_db::specs::{rule_example_with_witness, RuleExampleSpec};
365    use crate::export::SolutionPair;
366    vec![
367        RuleExampleSpec {
368            id: "decision_minimum_vertex_cover_to_minimum_vertex_cover",
369            build: || {
370                rule_example_with_witness::<_, MinimumVertexCover<SimpleGraph, i64>>(
371                    Decision::new(
372                        MinimumVertexCover::new(
373                            SimpleGraph::new(4, vec![(0, 1), (1, 2), (0, 2), (2, 3)]),
374                            vec![1i64; 4],
375                        ),
376                        2,
377                    ),
378                    SolutionPair {
379                        source_config: serde_json::json!([true, false, true, false]),
380                        target_config: serde_json::json!([true, false, true, false]),
381                    },
382                )
383            },
384        },
385        RuleExampleSpec {
386            id: "decision_minimum_vertex_cover_one_to_minimum_vertex_cover_one",
387            build: || {
388                rule_example_with_witness::<_, MinimumVertexCover<SimpleGraph, One>>(
389                    Decision::new(
390                        MinimumVertexCover::new(SimpleGraph::path(3), vec![One; 3]),
391                        1,
392                    ),
393                    SolutionPair {
394                        source_config: serde_json::json!([false, true, false]),
395                        target_config: serde_json::json!([false, true, false]),
396                    },
397                )
398            },
399        },
400    ]
401}
402
403/// Check if a set of vertices forms a vertex cover.
404///
405/// # Arguments
406/// * `graph` - The graph
407/// * `selected` - Boolean slice indicating which vertices are selected
408///
409/// # Panics
410/// Panics if `selected.len() != graph.num_vertices()`.
411#[cfg(test)]
412pub(crate) fn is_vertex_cover<G: Graph>(graph: &G, selected: &[bool]) -> bool {
413    assert_eq!(
414        selected.len(),
415        graph.num_vertices(),
416        "selected length must match num_vertices"
417    );
418    for (u, v) in graph.edges() {
419        if !selected[u] && !selected[v] {
420            return false;
421        }
422    }
423    true
424}
425
426#[cfg(test)]
427#[path = "../../unit_tests/models/graph/minimum_vertex_cover.rs"]
428mod tests;