problemreductions/models/graph/
independent_set.rs

1//! Independent Set problem implementation.
2//!
3//! The Independent Set problem asks for a maximum weight subset of vertices
4//! such that no two vertices in the subset are adjacent.
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 Independent Set 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/// - No two vertices in S are adjacent (independent set constraint)
18/// - The total weight Σ_{v ∈ S} w_v is maximized
19///
20/// # Example
21///
22/// ```
23/// use problemreductions::models::graph::IndependentSet;
24/// use problemreductions::{Problem, Solver, BruteForce};
25///
26/// // Create a triangle graph (3 vertices, 3 edges)
27/// let problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
28///
29/// // Solve with brute force
30/// let solver = BruteForce::new();
31/// let solutions = solver.find_best(&problem);
32///
33/// // Maximum independent set in a triangle has size 1
34/// assert!(solutions.iter().all(|s| s.iter().sum::<usize>() == 1));
35/// ```
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct IndependentSet<W = i32> {
38    /// The underlying graph.
39    graph: UnGraph<(), ()>,
40    /// Weights for each vertex.
41    weights: Vec<W>,
42}
43
44impl<W: Clone + Default> IndependentSet<W> {
45    /// Create a new Independent Set problem with unit weights.
46    ///
47    /// # Arguments
48    /// * `num_vertices` - Number of vertices in the graph
49    /// * `edges` - List of edges as (u, v) pairs
50    pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self
51    where
52        W: From<i32>,
53    {
54        let mut graph = UnGraph::new_undirected();
55        for _ in 0..num_vertices {
56            graph.add_node(());
57        }
58        for (u, v) in edges {
59            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
60        }
61        let weights = vec![W::from(1); num_vertices];
62        Self { graph, weights }
63    }
64
65    /// Create a new Independent Set problem with custom weights.
66    pub fn with_weights(num_vertices: usize, edges: Vec<(usize, usize)>, weights: Vec<W>) -> Self {
67        assert_eq!(
68            weights.len(),
69            num_vertices,
70            "weights length must match num_vertices"
71        );
72        let mut graph = UnGraph::new_undirected();
73        for _ in 0..num_vertices {
74            graph.add_node(());
75        }
76        for (u, v) in edges {
77            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
78        }
79        Self { graph, weights }
80    }
81
82    /// Get the number of vertices.
83    pub fn num_vertices(&self) -> usize {
84        self.graph.node_count()
85    }
86
87    /// Get the number of edges.
88    pub fn num_edges(&self) -> usize {
89        self.graph.edge_count()
90    }
91
92    /// Get the edges as a list of (u, v) pairs.
93    pub fn edges(&self) -> Vec<(usize, usize)> {
94        self.graph
95            .edge_references()
96            .map(|e| (e.source().index(), e.target().index()))
97            .collect()
98    }
99
100    /// Check if two vertices are adjacent.
101    pub fn has_edge(&self, u: usize, v: usize) -> bool {
102        self.graph
103            .find_edge(NodeIndex::new(u), NodeIndex::new(v))
104            .is_some()
105    }
106
107    /// Get a reference to the weights vector.
108    pub fn weights_ref(&self) -> &Vec<W> {
109        &self.weights
110    }
111}
112
113impl<W> Problem for IndependentSet<W>
114where
115    W: Clone + Default + PartialOrd + num_traits::Num + num_traits::Zero + std::ops::AddAssign + 'static,
116{
117    const NAME: &'static str = "IndependentSet";
118    type GraphType = SimpleGraph;
119    type Weight = W;
120    type Size = W;
121
122    fn num_variables(&self) -> usize {
123        self.graph.node_count()
124    }
125
126    fn num_flavors(&self) -> usize {
127        2 // Binary: 0 = not in set, 1 = in set
128    }
129
130    fn problem_size(&self) -> ProblemSize {
131        ProblemSize::new(vec![
132            ("num_vertices", self.graph.node_count()),
133            ("num_edges", self.graph.edge_count()),
134        ])
135    }
136
137    fn energy_mode(&self) -> EnergyMode {
138        EnergyMode::LargerSizeIsBetter // Maximize total weight
139    }
140
141    fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
142        let is_valid = is_independent_set_config(&self.graph, config);
143        let mut total = W::zero();
144        for (i, &selected) in config.iter().enumerate() {
145            if selected == 1 {
146                total += self.weights[i].clone();
147            }
148        }
149        SolutionSize::new(total, is_valid)
150    }
151}
152
153impl<W> ConstraintSatisfactionProblem for IndependentSet<W>
154where
155    W: Clone + Default + PartialOrd + num_traits::Num + num_traits::Zero + std::ops::AddAssign + 'static,
156{
157    fn constraints(&self) -> Vec<LocalConstraint> {
158        // For each edge (u, v), at most one of u, v can be selected
159        // Valid configs: (0,0), (0,1), (1,0) but not (1,1)
160        self.graph
161            .edge_references()
162            .map(|e| {
163                LocalConstraint::new(
164                    2,
165                    vec![e.source().index(), e.target().index()],
166                    vec![true, true, true, false], // (0,0), (0,1), (1,0), (1,1)
167                )
168            })
169            .collect()
170    }
171
172    fn objectives(&self) -> Vec<LocalSolutionSize<Self::Size>> {
173        // Each vertex contributes its weight if selected
174        self.weights
175            .iter()
176            .enumerate()
177            .map(|(i, w)| LocalSolutionSize::new(2, vec![i], vec![W::zero(), w.clone()]))
178            .collect()
179    }
180
181    fn weights(&self) -> Vec<Self::Size> {
182        self.weights.clone()
183    }
184
185    fn set_weights(&mut self, weights: Vec<Self::Size>) {
186        assert_eq!(weights.len(), self.num_variables());
187        self.weights = weights;
188    }
189
190    fn is_weighted(&self) -> bool {
191        // Check if all weights are the same
192        if self.weights.is_empty() {
193            return false;
194        }
195        let first = &self.weights[0];
196        !self.weights.iter().all(|w| w == first)
197    }
198}
199
200/// Check if a configuration forms a valid independent set.
201fn is_independent_set_config(graph: &UnGraph<(), ()>, config: &[usize]) -> bool {
202    for edge in graph.edge_references() {
203        let u = edge.source().index();
204        let v = edge.target().index();
205        if config.get(u).copied().unwrap_or(0) == 1 && config.get(v).copied().unwrap_or(0) == 1 {
206            return false;
207        }
208    }
209    true
210}
211
212/// Check if a set of vertices forms an independent set.
213///
214/// # Arguments
215/// * `num_vertices` - Total number of vertices
216/// * `edges` - List of edges as (u, v) pairs
217/// * `selected` - Boolean slice indicating which vertices are selected
218pub fn is_independent_set(
219    num_vertices: usize,
220    edges: &[(usize, usize)],
221    selected: &[bool],
222) -> bool {
223    if selected.len() != num_vertices {
224        return false;
225    }
226    for &(u, v) in edges {
227        if u < selected.len() && v < selected.len() && selected[u] && selected[v] {
228            return false;
229        }
230    }
231    true
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use crate::solvers::{BruteForce, Solver};
238
239    #[test]
240    fn test_independent_set_creation() {
241        let problem = IndependentSet::<i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
242        assert_eq!(problem.num_vertices(), 4);
243        assert_eq!(problem.num_edges(), 3);
244        assert_eq!(problem.num_variables(), 4);
245        assert_eq!(problem.num_flavors(), 2);
246    }
247
248    #[test]
249    fn test_independent_set_with_weights() {
250        let problem = IndependentSet::with_weights(3, vec![(0, 1)], vec![1, 2, 3]);
251        assert_eq!(problem.weights(), vec![1, 2, 3]);
252        assert!(problem.is_weighted());
253    }
254
255    #[test]
256    fn test_independent_set_unweighted() {
257        let problem = IndependentSet::<i32>::new(3, vec![(0, 1)]);
258        assert!(!problem.is_weighted());
259    }
260
261    #[test]
262    fn test_has_edge() {
263        let problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2)]);
264        assert!(problem.has_edge(0, 1));
265        assert!(problem.has_edge(1, 0)); // Undirected
266        assert!(problem.has_edge(1, 2));
267        assert!(!problem.has_edge(0, 2));
268    }
269
270    #[test]
271    fn test_solution_size_valid() {
272        let problem = IndependentSet::<i32>::new(4, vec![(0, 1), (2, 3)]);
273
274        // Valid: select 0 and 2 (not adjacent)
275        let sol = problem.solution_size(&[1, 0, 1, 0]);
276        assert!(sol.is_valid);
277        assert_eq!(sol.size, 2);
278
279        // Valid: select 1 and 3 (not adjacent)
280        let sol = problem.solution_size(&[0, 1, 0, 1]);
281        assert!(sol.is_valid);
282        assert_eq!(sol.size, 2);
283    }
284
285    #[test]
286    fn test_solution_size_invalid() {
287        let problem = IndependentSet::<i32>::new(4, vec![(0, 1), (2, 3)]);
288
289        // Invalid: 0 and 1 are adjacent
290        let sol = problem.solution_size(&[1, 1, 0, 0]);
291        assert!(!sol.is_valid);
292        assert_eq!(sol.size, 2);
293
294        // Invalid: 2 and 3 are adjacent
295        let sol = problem.solution_size(&[0, 0, 1, 1]);
296        assert!(!sol.is_valid);
297    }
298
299    #[test]
300    fn test_solution_size_empty() {
301        let problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2)]);
302        let sol = problem.solution_size(&[0, 0, 0]);
303        assert!(sol.is_valid);
304        assert_eq!(sol.size, 0);
305    }
306
307    #[test]
308    fn test_weighted_solution() {
309        let problem = IndependentSet::with_weights(3, vec![(0, 1)], vec![10, 20, 30]);
310
311        // Select vertex 2 (weight 30)
312        let sol = problem.solution_size(&[0, 0, 1]);
313        assert!(sol.is_valid);
314        assert_eq!(sol.size, 30);
315
316        // Select vertices 0 and 2 (weights 10 + 30 = 40)
317        let sol = problem.solution_size(&[1, 0, 1]);
318        assert!(sol.is_valid);
319        assert_eq!(sol.size, 40);
320    }
321
322    #[test]
323    fn test_constraints() {
324        let problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2)]);
325        let constraints = problem.constraints();
326        assert_eq!(constraints.len(), 2); // One per edge
327    }
328
329    #[test]
330    fn test_objectives() {
331        let problem = IndependentSet::with_weights(3, vec![(0, 1)], vec![5, 10, 15]);
332        let objectives = problem.objectives();
333        assert_eq!(objectives.len(), 3); // One per vertex
334    }
335
336    #[test]
337    fn test_brute_force_triangle() {
338        // Triangle graph: maximum IS has size 1
339        let problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
340        let solver = BruteForce::new();
341
342        let solutions = solver.find_best(&problem);
343        // All solutions should have exactly 1 vertex selected
344        assert_eq!(solutions.len(), 3); // Three equivalent solutions
345        for sol in &solutions {
346            assert_eq!(sol.iter().sum::<usize>(), 1);
347        }
348    }
349
350    #[test]
351    fn test_brute_force_path() {
352        // Path graph 0-1-2-3: maximum IS = {0,2} or {1,3} or {0,3}
353        let problem = IndependentSet::<i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
354        let solver = BruteForce::new();
355
356        let solutions = solver.find_best(&problem);
357        // Maximum size is 2
358        for sol in &solutions {
359            let size: usize = sol.iter().sum();
360            assert_eq!(size, 2);
361            // Verify it's valid
362            let sol_result = problem.solution_size(sol);
363            assert!(sol_result.is_valid);
364        }
365    }
366
367    #[test]
368    fn test_brute_force_weighted() {
369        // Graph with weights: vertex 1 has high weight but is connected to both 0 and 2
370        let problem = IndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![1, 100, 1]);
371        let solver = BruteForce::new();
372
373        let solutions = solver.find_best(&problem);
374        assert_eq!(solutions.len(), 1);
375        // Should select vertex 1 (weight 100) over vertices 0+2 (weight 2)
376        assert_eq!(solutions[0], vec![0, 1, 0]);
377    }
378
379    #[test]
380    fn test_is_independent_set_function() {
381        assert!(is_independent_set(3, &[(0, 1)], &[true, false, true]));
382        assert!(is_independent_set(3, &[(0, 1)], &[false, true, true]));
383        assert!(!is_independent_set(3, &[(0, 1)], &[true, true, false]));
384        assert!(is_independent_set(
385            3,
386            &[(0, 1), (1, 2)],
387            &[true, false, true]
388        ));
389        assert!(!is_independent_set(
390            3,
391            &[(0, 1), (1, 2)],
392            &[false, true, true]
393        ));
394    }
395
396    #[test]
397    fn test_problem_size() {
398        let problem = IndependentSet::<i32>::new(5, vec![(0, 1), (1, 2), (2, 3)]);
399        let size = problem.problem_size();
400        assert_eq!(size.get("num_vertices"), Some(5));
401        assert_eq!(size.get("num_edges"), Some(3));
402    }
403
404    #[test]
405    fn test_energy_mode() {
406        let problem = IndependentSet::<i32>::new(3, vec![(0, 1)]);
407        assert!(problem.energy_mode().is_maximization());
408    }
409
410    #[test]
411    fn test_edges() {
412        let problem = IndependentSet::<i32>::new(4, vec![(0, 1), (2, 3)]);
413        let edges = problem.edges();
414        assert_eq!(edges.len(), 2);
415        assert!(edges.contains(&(0, 1)) || edges.contains(&(1, 0)));
416        assert!(edges.contains(&(2, 3)) || edges.contains(&(3, 2)));
417    }
418
419    #[test]
420    fn test_set_weights() {
421        let mut problem = IndependentSet::<i32>::new(3, vec![(0, 1)]);
422        problem.set_weights(vec![5, 10, 15]);
423        assert_eq!(problem.weights(), vec![5, 10, 15]);
424    }
425
426    #[test]
427    fn test_empty_graph() {
428        let problem = IndependentSet::<i32>::new(3, vec![]);
429        let solver = BruteForce::new();
430
431        let solutions = solver.find_best(&problem);
432        assert_eq!(solutions.len(), 1);
433        // All vertices can be selected
434        assert_eq!(solutions[0], vec![1, 1, 1]);
435    }
436
437    #[test]
438    fn test_is_satisfied() {
439        let problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2)]);
440
441        assert!(problem.is_satisfied(&[1, 0, 1])); // Valid IS
442        assert!(problem.is_satisfied(&[0, 1, 0])); // Valid IS
443        assert!(!problem.is_satisfied(&[1, 1, 0])); // Invalid: 0-1 adjacent
444        assert!(!problem.is_satisfied(&[0, 1, 1])); // Invalid: 1-2 adjacent
445    }
446}