problemreductions/models/graph/
dominating_set.rs

1//! Dominating Set problem implementation.
2//!
3//! The Dominating Set problem asks for a minimum weight subset of vertices
4//! such that every vertex is either in the set or adjacent to a vertex in the set.
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 serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13/// The Dominating Set problem.
14///
15/// Given a graph G = (V, E) and weights w_v for each vertex,
16/// find a subset D ⊆ V such that:
17/// - Every vertex is either in D or adjacent to a vertex in D (domination)
18/// - The total weight Σ_{v ∈ D} w_v is minimized
19///
20/// # Example
21///
22/// ```
23/// use problemreductions::models::graph::DominatingSet;
24/// use problemreductions::{Problem, Solver, BruteForce};
25///
26/// // Star graph: center dominates all
27/// let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (0, 2), (0, 3)]);
28///
29/// let solver = BruteForce::new();
30/// let solutions = solver.find_best(&problem);
31///
32/// // Minimum dominating set is just the center vertex
33/// assert!(solutions.contains(&vec![1, 0, 0, 0]));
34/// ```
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct DominatingSet<W = i32> {
37    /// The underlying graph.
38    graph: UnGraph<(), ()>,
39    /// Weights for each vertex.
40    weights: Vec<W>,
41}
42
43impl<W: Clone + Default> DominatingSet<W> {
44    /// Create a new Dominating Set problem with unit weights.
45    pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self
46    where
47        W: From<i32>,
48    {
49        let mut graph = UnGraph::new_undirected();
50        for _ in 0..num_vertices {
51            graph.add_node(());
52        }
53        for (u, v) in edges {
54            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
55        }
56        let weights = vec![W::from(1); num_vertices];
57        Self { graph, weights }
58    }
59
60    /// Create a new Dominating Set problem with custom weights.
61    pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec<W>) -> Self {
62        assert_eq!(weights.len(), num_vertices);
63        let mut graph = UnGraph::new_undirected();
64        for _ in 0..num_vertices {
65            graph.add_node(());
66        }
67        for (u, v) in edges {
68            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
69        }
70        Self { graph, weights }
71    }
72
73    /// Get the number of vertices.
74    pub fn num_vertices(&self) -> usize {
75        self.graph.node_count()
76    }
77
78    /// Get the number of edges.
79    pub fn num_edges(&self) -> usize {
80        self.graph.edge_count()
81    }
82
83    /// Get neighbors of a vertex.
84    pub fn neighbors(&self, v: usize) -> Vec<usize> {
85        self.graph
86            .neighbors(NodeIndex::new(v))
87            .map(|n| n.index())
88            .collect()
89    }
90
91    /// Get the closed neighborhood `N[v] = {v} ∪ N(v)`.
92    pub fn closed_neighborhood(&self, v: usize) -> HashSet<usize> {
93        let mut neighborhood: HashSet<usize> = self.neighbors(v).into_iter().collect();
94        neighborhood.insert(v);
95        neighborhood
96    }
97
98    /// Check if a set of vertices is a dominating set.
99    fn is_dominating(&self, config: &[usize]) -> bool {
100        let n = self.graph.node_count();
101        let mut dominated = vec![false; n];
102
103        for (v, &selected) in config.iter().enumerate() {
104            if selected == 1 {
105                // v dominates itself
106                dominated[v] = true;
107                // v dominates all its neighbors
108                for neighbor in self.neighbors(v) {
109                    if neighbor < n {
110                        dominated[neighbor] = true;
111                    }
112                }
113            }
114        }
115
116        dominated.iter().all(|&d| d)
117    }
118}
119
120impl<W> Problem for DominatingSet<W>
121where
122    W: Clone + Default + PartialOrd + num_traits::Num + num_traits::Zero + std::ops::AddAssign + 'static,
123{
124    const NAME: &'static str = "DominatingSet";
125    type GraphType = SimpleGraph;
126    type Weight = W;
127    type Size = W;
128
129    fn num_variables(&self) -> usize {
130        self.graph.node_count()
131    }
132
133    fn num_flavors(&self) -> usize {
134        2
135    }
136
137    fn problem_size(&self) -> ProblemSize {
138        ProblemSize::new(vec![
139            ("num_vertices", self.graph.node_count()),
140            ("num_edges", self.graph.edge_count()),
141        ])
142    }
143
144    fn energy_mode(&self) -> EnergyMode {
145        EnergyMode::SmallerSizeIsBetter // Minimize total weight
146    }
147
148    fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
149        let is_valid = self.is_dominating(config);
150        let mut total = W::zero();
151        for (i, &selected) in config.iter().enumerate() {
152            if selected == 1 {
153                total += self.weights[i].clone();
154            }
155        }
156        SolutionSize::new(total, is_valid)
157    }
158}
159
160impl<W> ConstraintSatisfactionProblem for DominatingSet<W>
161where
162    W: Clone + Default + PartialOrd + num_traits::Num + num_traits::Zero + std::ops::AddAssign + 'static,
163{
164    fn constraints(&self) -> Vec<LocalConstraint> {
165        // For each vertex v, at least one vertex in N[v] must be selected
166        (0..self.graph.node_count())
167            .map(|v| {
168                let closed_nbhd: Vec<usize> = self.closed_neighborhood(v).into_iter().collect();
169                let num_vars = closed_nbhd.len();
170                let num_configs = 2usize.pow(num_vars as u32);
171
172                // All configs are valid except all-zeros
173                let mut spec = vec![true; num_configs];
174                spec[0] = false;
175
176                LocalConstraint::new(2, closed_nbhd, spec)
177            })
178            .collect()
179    }
180
181    fn objectives(&self) -> Vec<LocalSolutionSize<Self::Size>> {
182        self.weights
183            .iter()
184            .enumerate()
185            .map(|(i, w)| LocalSolutionSize::new(2, vec![i], vec![W::zero(), w.clone()]))
186            .collect()
187    }
188
189    fn weights(&self) -> Vec<Self::Size> {
190        self.weights.clone()
191    }
192
193    fn set_weights(&mut self, weights: Vec<Self::Size>) {
194        assert_eq!(weights.len(), self.num_variables());
195        self.weights = weights;
196    }
197
198    fn is_weighted(&self) -> bool {
199        if self.weights.is_empty() {
200            return false;
201        }
202        let first = &self.weights[0];
203        !self.weights.iter().all(|w| w == first)
204    }
205}
206
207/// Check if a set of vertices is a dominating set.
208pub fn is_dominating_set(num_vertices: usize, edges: &[(usize, usize)], selected: &[bool]) -> bool {
209    if selected.len() != num_vertices {
210        return false;
211    }
212
213    // Build adjacency list
214    let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); num_vertices];
215    for &(u, v) in edges {
216        if u < num_vertices && v < num_vertices {
217            adj[u].insert(v);
218            adj[v].insert(u);
219        }
220    }
221
222    // Check each vertex is dominated
223    for v in 0..num_vertices {
224        if selected[v] {
225            continue; // v dominates itself
226        }
227        // Check if any neighbor of v is selected
228        let dominated = adj[v].iter().any(|&u| selected[u]);
229        if !dominated {
230            return false;
231        }
232    }
233
234    true
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::solvers::{BruteForce, Solver};
241
242    #[test]
243    fn test_dominating_set_creation() {
244        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
245        assert_eq!(problem.num_vertices(), 4);
246        assert_eq!(problem.num_edges(), 3);
247    }
248
249    #[test]
250    fn test_dominating_set_with_weights() {
251        let problem = DominatingSet::with_weights(3, vec![(0, 1)], vec![1, 2, 3]);
252        assert_eq!(problem.weights(), vec![1, 2, 3]);
253    }
254
255    #[test]
256    fn test_neighbors() {
257        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (0, 2), (1, 2)]);
258        let nbrs = problem.neighbors(0);
259        assert!(nbrs.contains(&1));
260        assert!(nbrs.contains(&2));
261        assert!(!nbrs.contains(&3));
262    }
263
264    #[test]
265    fn test_closed_neighborhood() {
266        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (0, 2)]);
267        let cn = problem.closed_neighborhood(0);
268        assert!(cn.contains(&0));
269        assert!(cn.contains(&1));
270        assert!(cn.contains(&2));
271        assert!(!cn.contains(&3));
272    }
273
274    #[test]
275    fn test_solution_size_valid() {
276        // Star graph: center dominates all
277        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (0, 2), (0, 3)]);
278
279        // Select center
280        let sol = problem.solution_size(&[1, 0, 0, 0]);
281        assert!(sol.is_valid);
282        assert_eq!(sol.size, 1);
283
284        // Select all leaves
285        let sol = problem.solution_size(&[0, 1, 1, 1]);
286        assert!(sol.is_valid);
287        assert_eq!(sol.size, 3);
288    }
289
290    #[test]
291    fn test_solution_size_invalid() {
292        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (2, 3)]);
293
294        // Select none
295        let sol = problem.solution_size(&[0, 0, 0, 0]);
296        assert!(!sol.is_valid);
297
298        // Select only vertex 0 (doesn't dominate 2, 3)
299        let sol = problem.solution_size(&[1, 0, 0, 0]);
300        assert!(!sol.is_valid);
301    }
302
303    #[test]
304    fn test_brute_force_star() {
305        // Star graph: minimum dominating set is the center
306        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (0, 2), (0, 3)]);
307        let solver = BruteForce::new();
308
309        let solutions = solver.find_best(&problem);
310        assert!(solutions.contains(&vec![1, 0, 0, 0]));
311        for sol in &solutions {
312            assert_eq!(problem.solution_size(sol).size, 1);
313        }
314    }
315
316    #[test]
317    fn test_brute_force_path() {
318        // Path 0-1-2-3-4: need to dominate all 5 vertices
319        let problem = DominatingSet::<i32>::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
320        let solver = BruteForce::new();
321
322        let solutions = solver.find_best(&problem);
323        // Minimum is 2 (e.g., vertices 1 and 3)
324        for sol in &solutions {
325            assert_eq!(problem.solution_size(sol).size, 2);
326            assert!(problem.solution_size(sol).is_valid);
327        }
328    }
329
330    #[test]
331    fn test_brute_force_weighted() {
332        // Star with heavy center
333        let problem =
334            DominatingSet::with_weights(4, vec![(0, 1), (0, 2), (0, 3)], vec![100, 1, 1, 1]);
335        let solver = BruteForce::new();
336
337        let solutions = solver.find_best(&problem);
338        // Prefer selecting all leaves (3) over center (100)
339        assert_eq!(solutions.len(), 1);
340        assert_eq!(solutions[0], vec![0, 1, 1, 1]);
341    }
342
343    #[test]
344    fn test_is_dominating_set_function() {
345        let edges = vec![(0, 1), (0, 2), (0, 3)];
346
347        // Center dominates all
348        assert!(is_dominating_set(4, &edges, &[true, false, false, false]));
349        // All leaves dominate (leaf dominates center which dominates others)
350        assert!(is_dominating_set(4, &edges, &[false, true, true, true]));
351        // Single leaf doesn't dominate other leaves
352        assert!(!is_dominating_set(4, &edges, &[false, true, false, false]));
353        // Empty doesn't dominate
354        assert!(!is_dominating_set(4, &edges, &[false, false, false, false]));
355    }
356
357    #[test]
358    fn test_constraints() {
359        let problem = DominatingSet::<i32>::new(3, vec![(0, 1), (1, 2)]);
360        let constraints = problem.constraints();
361        assert_eq!(constraints.len(), 3); // One per vertex
362    }
363
364    #[test]
365    fn test_energy_mode() {
366        let problem = DominatingSet::<i32>::new(2, vec![(0, 1)]);
367        assert!(problem.energy_mode().is_minimization());
368    }
369
370    #[test]
371    fn test_isolated_vertex() {
372        // Isolated vertex must be in dominating set
373        let problem = DominatingSet::<i32>::new(3, vec![(0, 1)]);
374        let solver = BruteForce::new();
375
376        let solutions = solver.find_best(&problem);
377        // Vertex 2 is isolated, must be selected
378        for sol in &solutions {
379            assert_eq!(sol[2], 1);
380            assert!(problem.solution_size(sol).is_valid);
381        }
382    }
383
384    #[test]
385    fn test_is_satisfied() {
386        let problem = DominatingSet::<i32>::new(4, vec![(0, 1), (0, 2), (0, 3)]);
387
388        assert!(problem.is_satisfied(&[1, 0, 0, 0])); // Center dominates all
389        assert!(problem.is_satisfied(&[0, 1, 1, 1])); // Leaves dominate
390        assert!(!problem.is_satisfied(&[0, 1, 0, 0])); // Missing 2 and 3
391    }
392
393    #[test]
394    fn test_objectives() {
395        let problem = DominatingSet::with_weights(3, vec![(0, 1)], vec![5, 10, 15]);
396        let objectives = problem.objectives();
397        assert_eq!(objectives.len(), 3);
398    }
399
400    #[test]
401    fn test_set_weights() {
402        let mut problem = DominatingSet::<i32>::new(3, vec![(0, 1)]);
403        assert!(!problem.is_weighted()); // Initially uniform
404        problem.set_weights(vec![1, 2, 3]);
405        assert!(problem.is_weighted());
406        assert_eq!(problem.weights(), vec![1, 2, 3]);
407    }
408
409    #[test]
410    fn test_is_weighted_empty() {
411        let problem = DominatingSet::<i32>::with_weights(0, vec![], vec![]);
412        assert!(!problem.is_weighted());
413    }
414
415    #[test]
416    fn test_is_dominating_set_wrong_len() {
417        assert!(!is_dominating_set(3, &[(0, 1)], &[true, false]));
418    }
419
420    #[test]
421    fn test_problem_size() {
422        let problem = DominatingSet::<i32>::new(5, vec![(0, 1), (1, 2), (2, 3)]);
423        let size = problem.problem_size();
424        assert_eq!(size.get("num_vertices"), Some(5));
425        assert_eq!(size.get("num_edges"), Some(3));
426    }
427}