problemreductions/models/graph/
vertex_covering.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::graph_types::SimpleGraph;
7use crate::traits::{ConstraintSatisfactionProblem, Problem};
8use crate::types::{EnergyMode, LocalConstraint, LocalSolutionSize, ProblemSize, SolutionSize};
9use petgraph::graph::{NodeIndex, UnGraph};
10use petgraph::visit::EdgeRef;
11use serde::{Deserialize, Serialize};
12
13/// The Vertex Covering problem.
14///
15/// Given a graph G = (V, E) and weights w_v for each vertex,
16/// find a subset S ⊆ V such that:
17/// - Every edge has at least one endpoint in S (covering constraint)
18/// - The total weight Σ_{v ∈ S} w_v is minimized
19///
20/// # Example
21///
22/// ```
23/// use problemreductions::models::graph::VertexCovering;
24/// use problemreductions::{Problem, Solver, BruteForce};
25///
26/// // Create a path graph 0-1-2
27/// let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2)]);
28///
29/// // Solve with brute force
30/// let solver = BruteForce::new();
31/// let solutions = solver.find_best(&problem);
32///
33/// // Minimum vertex cover is just vertex 1
34/// assert!(solutions.contains(&vec![0, 1, 0]));
35/// ```
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct VertexCovering<W = i32> {
38    /// The underlying graph.
39    graph: UnGraph<(), ()>,
40    /// Weights for each vertex.
41    weights: Vec<W>,
42}
43
44impl<W: Clone + Default> VertexCovering<W> {
45    /// Create a new Vertex Covering problem with unit weights.
46    pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self
47    where
48        W: From<i32>,
49    {
50        let mut graph = UnGraph::new_undirected();
51        for _ in 0..num_vertices {
52            graph.add_node(());
53        }
54        for (u, v) in edges {
55            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
56        }
57        let weights = vec![W::from(1); num_vertices];
58        Self { graph, weights }
59    }
60
61    /// Create a new Vertex Covering problem with custom weights.
62    pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec<W>) -> Self {
63        assert_eq!(weights.len(), num_vertices);
64        let mut graph = UnGraph::new_undirected();
65        for _ in 0..num_vertices {
66            graph.add_node(());
67        }
68        for (u, v) in edges {
69            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
70        }
71        Self { graph, weights }
72    }
73
74    /// Get the number of vertices.
75    pub fn num_vertices(&self) -> usize {
76        self.graph.node_count()
77    }
78
79    /// Get the number of edges.
80    pub fn num_edges(&self) -> usize {
81        self.graph.edge_count()
82    }
83
84    /// Get the edges as a list of (u, v) pairs.
85    pub fn edges(&self) -> Vec<(usize, usize)> {
86        self.graph
87            .edge_references()
88            .map(|e| (e.source().index(), e.target().index()))
89            .collect()
90    }
91
92    /// Get a reference to the weights vector.
93    pub fn weights_ref(&self) -> &Vec<W> {
94        &self.weights
95    }
96}
97
98impl<W> Problem for VertexCovering<W>
99where
100    W: Clone + Default + PartialOrd + num_traits::Num + num_traits::Zero + std::ops::AddAssign + 'static,
101{
102    const NAME: &'static str = "VertexCovering";
103    type GraphType = SimpleGraph;
104    type Weight = W;
105    type Size = W;
106
107    fn num_variables(&self) -> usize {
108        self.graph.node_count()
109    }
110
111    fn num_flavors(&self) -> usize {
112        2
113    }
114
115    fn problem_size(&self) -> ProblemSize {
116        ProblemSize::new(vec![
117            ("num_vertices", self.graph.node_count()),
118            ("num_edges", self.graph.edge_count()),
119        ])
120    }
121
122    fn energy_mode(&self) -> EnergyMode {
123        EnergyMode::SmallerSizeIsBetter // Minimize total weight
124    }
125
126    fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
127        let is_valid = is_vertex_cover_config(&self.graph, config);
128        let mut total = W::zero();
129        for (i, &selected) in config.iter().enumerate() {
130            if selected == 1 {
131                total += self.weights[i].clone();
132            }
133        }
134        SolutionSize::new(total, is_valid)
135    }
136}
137
138impl<W> ConstraintSatisfactionProblem for VertexCovering<W>
139where
140    W: Clone + Default + PartialOrd + num_traits::Num + num_traits::Zero + std::ops::AddAssign + 'static,
141{
142    fn constraints(&self) -> Vec<LocalConstraint> {
143        // For each edge (u, v), at least one of u, v must be selected
144        // Valid configs: (0,1), (1,0), (1,1) but not (0,0)
145        self.graph
146            .edge_references()
147            .map(|e| {
148                LocalConstraint::new(
149                    2,
150                    vec![e.source().index(), e.target().index()],
151                    vec![false, true, true, true], // (0,0), (0,1), (1,0), (1,1)
152                )
153            })
154            .collect()
155    }
156
157    fn objectives(&self) -> Vec<LocalSolutionSize<Self::Size>> {
158        // Each vertex contributes its weight if selected (to be minimized)
159        self.weights
160            .iter()
161            .enumerate()
162            .map(|(i, w)| LocalSolutionSize::new(2, vec![i], vec![W::zero(), w.clone()]))
163            .collect()
164    }
165
166    fn weights(&self) -> Vec<Self::Size> {
167        self.weights.clone()
168    }
169
170    fn set_weights(&mut self, weights: Vec<Self::Size>) {
171        assert_eq!(weights.len(), self.num_variables());
172        self.weights = weights;
173    }
174
175    fn is_weighted(&self) -> bool {
176        if self.weights.is_empty() {
177            return false;
178        }
179        let first = &self.weights[0];
180        !self.weights.iter().all(|w| w == first)
181    }
182}
183
184/// Check if a configuration forms a valid vertex cover.
185fn is_vertex_cover_config(graph: &UnGraph<(), ()>, config: &[usize]) -> bool {
186    for edge in graph.edge_references() {
187        let u = edge.source().index();
188        let v = edge.target().index();
189        let u_covered = config.get(u).copied().unwrap_or(0) == 1;
190        let v_covered = config.get(v).copied().unwrap_or(0) == 1;
191        if !u_covered && !v_covered {
192            return false;
193        }
194    }
195    true
196}
197
198/// Check if a set of vertices forms a vertex cover.
199///
200/// # Arguments
201/// * `num_vertices` - Total number of vertices
202/// * `edges` - List of edges as (u, v) pairs
203/// * `selected` - Boolean slice indicating which vertices are selected
204pub fn is_vertex_cover(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool]) -> bool {
205    if selected.len() != num_vertices {
206        return false;
207    }
208    for &(u, v) in edges {
209        if u < selected.len() && v < selected.len() && !selected[u] && !selected[v] {
210            return false;
211        }
212    }
213    true
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::solvers::{BruteForce, Solver};
220
221    #[test]
222    fn test_vertex_cover_creation() {
223        let problem = VertexCovering::<i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
224        assert_eq!(problem.num_vertices(), 4);
225        assert_eq!(problem.num_edges(), 3);
226        assert_eq!(problem.num_variables(), 4);
227        assert_eq!(problem.num_flavors(), 2);
228    }
229
230    #[test]
231    fn test_vertex_cover_with_weights() {
232        let problem = VertexCovering::with_weights(3, vec![(0, 1)], vec![1, 2, 3]);
233        assert_eq!(problem.weights(), vec![1, 2, 3]);
234        assert!(problem.is_weighted());
235    }
236
237    #[test]
238    fn test_solution_size_valid() {
239        let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2)]);
240
241        // Valid: select vertex 1 (covers both edges)
242        let sol = problem.solution_size(&[0, 1, 0]);
243        assert!(sol.is_valid);
244        assert_eq!(sol.size, 1);
245
246        // Valid: select all vertices
247        let sol = problem.solution_size(&[1, 1, 1]);
248        assert!(sol.is_valid);
249        assert_eq!(sol.size, 3);
250    }
251
252    #[test]
253    fn test_solution_size_invalid() {
254        let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2)]);
255
256        // Invalid: no vertex selected
257        let sol = problem.solution_size(&[0, 0, 0]);
258        assert!(!sol.is_valid);
259
260        // Invalid: only vertex 0 selected (edge 1-2 not covered)
261        let sol = problem.solution_size(&[1, 0, 0]);
262        assert!(!sol.is_valid);
263    }
264
265    #[test]
266    fn test_brute_force_path() {
267        // Path graph 0-1-2: minimum vertex cover is {1}
268        let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2)]);
269        let solver = BruteForce::new();
270
271        let solutions = solver.find_best(&problem);
272        assert_eq!(solutions.len(), 1);
273        assert_eq!(solutions[0], vec![0, 1, 0]);
274    }
275
276    #[test]
277    fn test_brute_force_triangle() {
278        // Triangle: minimum vertex cover has size 2
279        let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
280        let solver = BruteForce::new();
281
282        let solutions = solver.find_best(&problem);
283        // There are 3 minimum covers of size 2
284        assert_eq!(solutions.len(), 3);
285        for sol in &solutions {
286            assert_eq!(sol.iter().sum::<usize>(), 2);
287            assert!(problem.solution_size(sol).is_valid);
288        }
289    }
290
291    #[test]
292    fn test_brute_force_weighted() {
293        // Weighted: prefer selecting low-weight vertices
294        let problem = VertexCovering::with_weights(3, vec![(0, 1), (1, 2)], vec![100, 1, 100]);
295        let solver = BruteForce::new();
296
297        let solutions = solver.find_best(&problem);
298        assert_eq!(solutions.len(), 1);
299        // Should select vertex 1 (weight 1) instead of 0 and 2 (total 200)
300        assert_eq!(solutions[0], vec![0, 1, 0]);
301    }
302
303    #[test]
304    fn test_is_vertex_cover_function() {
305        assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[false, true, false]));
306        assert!(is_vertex_cover(3, &[(0, 1), (1, 2)], &[true, false, true]));
307        assert!(!is_vertex_cover(
308            3,
309            &[(0, 1), (1, 2)],
310            &[true, false, false]
311        ));
312        assert!(!is_vertex_cover(
313            3,
314            &[(0, 1), (1, 2)],
315            &[false, false, false]
316        ));
317    }
318
319    #[test]
320    fn test_constraints() {
321        let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2)]);
322        let constraints = problem.constraints();
323        assert_eq!(constraints.len(), 2);
324    }
325
326    #[test]
327    fn test_energy_mode() {
328        let problem = VertexCovering::<i32>::new(3, vec![(0, 1)]);
329        assert!(problem.energy_mode().is_minimization());
330    }
331
332    #[test]
333    fn test_empty_graph() {
334        let problem = VertexCovering::<i32>::new(3, vec![]);
335        let solver = BruteForce::new();
336
337        let solutions = solver.find_best(&problem);
338        // No edges means empty cover is valid and optimal
339        assert_eq!(solutions.len(), 1);
340        assert_eq!(solutions[0], vec![0, 0, 0]);
341    }
342
343    #[test]
344    fn test_single_edge() {
345        let problem = VertexCovering::<i32>::new(2, vec![(0, 1)]);
346        let solver = BruteForce::new();
347
348        let solutions = solver.find_best(&problem);
349        // Either vertex covers the single edge
350        assert_eq!(solutions.len(), 2);
351    }
352
353    #[test]
354    fn test_is_satisfied() {
355        let problem = VertexCovering::<i32>::new(3, vec![(0, 1), (1, 2)]);
356
357        assert!(problem.is_satisfied(&[0, 1, 0])); // Valid cover
358        assert!(problem.is_satisfied(&[1, 0, 1])); // Valid cover
359        assert!(!problem.is_satisfied(&[1, 0, 0])); // Edge 1-2 uncovered
360        assert!(!problem.is_satisfied(&[0, 0, 1])); // Edge 0-1 uncovered
361    }
362
363    #[test]
364    fn test_complement_relationship() {
365        // For a graph, if S is an independent set, then V\S is a vertex cover
366        use crate::models::graph::IndependentSet;
367
368        let edges = vec![(0, 1), (1, 2), (2, 3)];
369        let is_problem = IndependentSet::<i32>::new(4, edges.clone());
370        let vc_problem = VertexCovering::<i32>::new(4, edges);
371
372        let solver = BruteForce::new();
373
374        let is_solutions = solver.find_best(&is_problem);
375        for is_sol in &is_solutions {
376            // Complement should be a valid vertex cover
377            let vc_config: Vec<usize> = is_sol.iter().map(|&x| 1 - x).collect();
378            assert!(vc_problem.solution_size(&vc_config).is_valid);
379        }
380    }
381
382    #[test]
383    fn test_objectives() {
384        let problem = VertexCovering::with_weights(3, vec![(0, 1)], vec![5, 10, 15]);
385        let objectives = problem.objectives();
386        assert_eq!(objectives.len(), 3);
387    }
388
389    #[test]
390    fn test_set_weights() {
391        let mut problem = VertexCovering::<i32>::new(3, vec![(0, 1)]);
392        assert!(!problem.is_weighted()); // Initially uniform
393        problem.set_weights(vec![1, 2, 3]);
394        assert!(problem.is_weighted());
395        assert_eq!(problem.weights(), vec![1, 2, 3]);
396    }
397
398    #[test]
399    fn test_is_weighted_empty() {
400        let problem = VertexCovering::<i32>::new(0, vec![]);
401        assert!(!problem.is_weighted());
402    }
403
404    #[test]
405    fn test_is_vertex_cover_wrong_len() {
406        // Wrong length should return false
407        assert!(!is_vertex_cover(3, &[(0, 1)], &[true, false]));
408    }
409}