problemreductions/models/graph/
coloring.rs

1//! Graph Coloring problem implementation.
2//!
3//! The K-Coloring problem asks whether a graph can be colored with K colors
4//! such that no two adjacent vertices have the same color.
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 Graph K-Coloring problem.
14///
15/// Given a graph G = (V, E) and K colors, find an assignment of colors
16/// to vertices such that no two adjacent vertices have the same color.
17///
18/// # Example
19///
20/// ```
21/// use problemreductions::models::graph::Coloring;
22/// use problemreductions::{Problem, Solver, BruteForce};
23///
24/// // Triangle graph needs at least 3 colors
25/// let problem = Coloring::new(3, 3, vec![(0, 1), (1, 2), (0, 2)]);
26///
27/// let solver = BruteForce::new();
28/// let solutions = solver.find_best(&problem);
29///
30/// // Verify all solutions are valid colorings
31/// for sol in &solutions {
32///     assert!(problem.solution_size(sol).is_valid);
33/// }
34/// ```
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Coloring {
37    /// Number of colors.
38    num_colors: usize,
39    /// The underlying graph.
40    graph: UnGraph<(), ()>,
41}
42
43impl Coloring {
44    /// Create a new K-Coloring problem.
45    ///
46    /// # Arguments
47    /// * `num_vertices` - Number of vertices
48    /// * `num_colors` - Number of available colors (K)
49    /// * `edges` - List of edges as (u, v) pairs
50    pub fn new(num_vertices: usize, num_colors: usize, edges: Vec<(usize, usize)>) -> Self {
51        let mut graph = UnGraph::new_undirected();
52        for _ in 0..num_vertices {
53            graph.add_node(());
54        }
55        for (u, v) in edges {
56            graph.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
57        }
58        Self { num_colors, graph }
59    }
60
61    /// Get the number of vertices.
62    pub fn num_vertices(&self) -> usize {
63        self.graph.node_count()
64    }
65
66    /// Get the number of edges.
67    pub fn num_edges(&self) -> usize {
68        self.graph.edge_count()
69    }
70
71    /// Get the number of colors.
72    pub fn num_colors(&self) -> usize {
73        self.num_colors
74    }
75
76    /// Get the edges as a list of (u, v) pairs.
77    pub fn edges(&self) -> Vec<(usize, usize)> {
78        self.graph
79            .edge_references()
80            .map(|e| (e.source().index(), e.target().index()))
81            .collect()
82    }
83
84    /// Check if a coloring is valid.
85    fn is_valid_coloring(&self, config: &[usize]) -> bool {
86        for edge in self.graph.edge_references() {
87            let u = edge.source().index();
88            let v = edge.target().index();
89            let color_u = config.get(u).copied().unwrap_or(0);
90            let color_v = config.get(v).copied().unwrap_or(0);
91            if color_u == color_v {
92                return false;
93            }
94        }
95        true
96    }
97}
98
99impl Problem for Coloring {
100    const NAME: &'static str = "Coloring";
101    type GraphType = SimpleGraph;
102    type Weight = i32;
103    type Size = i32;
104
105    fn num_variables(&self) -> usize {
106        self.graph.node_count()
107    }
108
109    fn num_flavors(&self) -> usize {
110        self.num_colors
111    }
112
113    fn problem_size(&self) -> ProblemSize {
114        ProblemSize::new(vec![
115            ("num_vertices", self.graph.node_count()),
116            ("num_edges", self.graph.edge_count()),
117            ("num_colors", self.num_colors),
118        ])
119    }
120
121    fn energy_mode(&self) -> EnergyMode {
122        // For decision problem, we just want any valid coloring
123        // Size = 0 for valid, 1 for invalid (minimize)
124        EnergyMode::SmallerSizeIsBetter
125    }
126
127    fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
128        let is_valid = self.is_valid_coloring(config);
129        // Count conflicts
130        let mut conflicts = 0;
131        for edge in self.graph.edge_references() {
132            let u = edge.source().index();
133            let v = edge.target().index();
134            let color_u = config.get(u).copied().unwrap_or(0);
135            let color_v = config.get(v).copied().unwrap_or(0);
136            if color_u == color_v {
137                conflicts += 1;
138            }
139        }
140        SolutionSize::new(conflicts, is_valid)
141    }
142}
143
144impl ConstraintSatisfactionProblem for Coloring {
145    fn constraints(&self) -> Vec<LocalConstraint> {
146        // For each edge, the two endpoints must have different colors
147        self.graph
148            .edge_references()
149            .map(|e| {
150                let u = e.source().index();
151                let v = e.target().index();
152                let k = self.num_colors;
153
154                // Build spec: valid iff colors are different
155                let mut spec = vec![false; k * k];
156                for c1 in 0..k {
157                    for c2 in 0..k {
158                        spec[c1 * k + c2] = c1 != c2;
159                    }
160                }
161
162                LocalConstraint::new(k, vec![u, v], spec)
163            })
164            .collect()
165    }
166
167    fn objectives(&self) -> Vec<LocalSolutionSize<Self::Size>> {
168        // No objectives - this is a pure constraint satisfaction problem
169        vec![]
170    }
171
172    fn weights(&self) -> Vec<Self::Size> {
173        vec![]
174    }
175
176    fn set_weights(&mut self, _weights: Vec<Self::Size>) {}
177
178    fn is_weighted(&self) -> bool {
179        false
180    }
181}
182
183/// Check if a coloring is valid for a graph.
184pub fn is_valid_coloring(
185    num_vertices: usize,
186    edges: &[(usize, usize)],
187    coloring: &[usize],
188    num_colors: usize,
189) -> bool {
190    if coloring.len() != num_vertices {
191        return false;
192    }
193    // Check all colors are valid
194    if coloring.iter().any(|&c| c >= num_colors) {
195        return false;
196    }
197    // Check no adjacent vertices have same color
198    for &(u, v) in edges {
199        if u < coloring.len() && v < coloring.len() && coloring[u] == coloring[v] {
200            return false;
201        }
202    }
203    true
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::solvers::{BruteForce, Solver};
210
211    #[test]
212    fn test_coloring_creation() {
213        let problem = Coloring::new(4, 3, vec![(0, 1), (1, 2), (2, 3)]);
214        assert_eq!(problem.num_vertices(), 4);
215        assert_eq!(problem.num_edges(), 3);
216        assert_eq!(problem.num_colors(), 3);
217        assert_eq!(problem.num_variables(), 4);
218        assert_eq!(problem.num_flavors(), 3);
219    }
220
221    #[test]
222    fn test_solution_size_valid() {
223        let problem = Coloring::new(3, 3, vec![(0, 1), (1, 2)]);
224
225        // Valid: different colors on adjacent vertices
226        let sol = problem.solution_size(&[0, 1, 0]);
227        assert!(sol.is_valid);
228        assert_eq!(sol.size, 0);
229
230        let sol = problem.solution_size(&[0, 1, 2]);
231        assert!(sol.is_valid);
232        assert_eq!(sol.size, 0);
233    }
234
235    #[test]
236    fn test_solution_size_invalid() {
237        let problem = Coloring::new(3, 3, vec![(0, 1), (1, 2)]);
238
239        // Invalid: adjacent vertices have same color
240        let sol = problem.solution_size(&[0, 0, 1]);
241        assert!(!sol.is_valid);
242        assert_eq!(sol.size, 1); // 1 conflict
243
244        let sol = problem.solution_size(&[0, 0, 0]);
245        assert!(!sol.is_valid);
246        assert_eq!(sol.size, 2); // 2 conflicts
247    }
248
249    #[test]
250    fn test_brute_force_path() {
251        // Path graph can be 2-colored
252        let problem = Coloring::new(4, 2, vec![(0, 1), (1, 2), (2, 3)]);
253        let solver = BruteForce::new();
254
255        let solutions = solver.find_best(&problem);
256        // All solutions should be valid (0 conflicts)
257        for sol in &solutions {
258            assert!(problem.solution_size(sol).is_valid);
259        }
260    }
261
262    #[test]
263    fn test_brute_force_triangle() {
264        // Triangle needs 3 colors
265        let problem = Coloring::new(3, 3, vec![(0, 1), (1, 2), (0, 2)]);
266        let solver = BruteForce::new();
267
268        let solutions = solver.find_best(&problem);
269        for sol in &solutions {
270            assert!(problem.solution_size(sol).is_valid);
271            // All three vertices have different colors
272            assert_ne!(sol[0], sol[1]);
273            assert_ne!(sol[1], sol[2]);
274            assert_ne!(sol[0], sol[2]);
275        }
276    }
277
278    #[test]
279    fn test_triangle_2_colors() {
280        // Triangle cannot be 2-colored
281        let problem = Coloring::new(3, 2, vec![(0, 1), (1, 2), (0, 2)]);
282        let solver = BruteForce::new();
283
284        let solutions = solver.find_best(&problem);
285        // Best we can do is 1 conflict
286        for sol in &solutions {
287            assert!(!problem.solution_size(sol).is_valid);
288            assert_eq!(problem.solution_size(sol).size, 1);
289        }
290    }
291
292    #[test]
293    fn test_constraints() {
294        let problem = Coloring::new(3, 2, vec![(0, 1), (1, 2)]);
295        let constraints = problem.constraints();
296        assert_eq!(constraints.len(), 2); // One per edge
297    }
298
299    #[test]
300    fn test_energy_mode() {
301        let problem = Coloring::new(2, 2, vec![(0, 1)]);
302        assert!(problem.energy_mode().is_minimization());
303    }
304
305    #[test]
306    fn test_is_valid_coloring_function() {
307        let edges = vec![(0, 1), (1, 2)];
308
309        assert!(is_valid_coloring(3, &edges, &[0, 1, 0], 2));
310        assert!(is_valid_coloring(3, &edges, &[0, 1, 2], 3));
311        assert!(!is_valid_coloring(3, &edges, &[0, 0, 1], 2)); // 0-1 conflict
312        assert!(!is_valid_coloring(3, &edges, &[0, 1, 1], 2)); // 1-2 conflict
313        assert!(!is_valid_coloring(3, &edges, &[0, 1], 2)); // Wrong length
314        assert!(!is_valid_coloring(3, &edges, &[0, 2, 0], 2)); // Color out of range
315    }
316
317    #[test]
318    fn test_empty_graph() {
319        let problem = Coloring::new(3, 1, vec![]);
320        let solver = BruteForce::new();
321
322        let solutions = solver.find_best(&problem);
323        // Any coloring is valid when there are no edges
324        assert!(problem.solution_size(&solutions[0]).is_valid);
325    }
326
327    #[test]
328    fn test_complete_graph_k4() {
329        // K4 needs 4 colors
330        let problem = Coloring::new(4, 4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
331        let solver = BruteForce::new();
332
333        let solutions = solver.find_best(&problem);
334        for sol in &solutions {
335            assert!(problem.solution_size(sol).is_valid);
336        }
337    }
338
339    #[test]
340    fn test_is_satisfied() {
341        let problem = Coloring::new(3, 3, vec![(0, 1), (1, 2)]);
342
343        assert!(problem.is_satisfied(&[0, 1, 0]));
344        assert!(problem.is_satisfied(&[0, 1, 2]));
345        assert!(!problem.is_satisfied(&[0, 0, 1]));
346    }
347
348    #[test]
349    fn test_problem_size() {
350        let problem = Coloring::new(5, 3, vec![(0, 1), (1, 2)]);
351        let size = problem.problem_size();
352        assert_eq!(size.get("num_vertices"), Some(5));
353        assert_eq!(size.get("num_edges"), Some(2));
354        assert_eq!(size.get("num_colors"), Some(3));
355    }
356
357    #[test]
358    fn test_csp_methods() {
359        let problem = Coloring::new(3, 2, vec![(0, 1)]);
360
361        // Coloring has no objectives (pure CSP)
362        let objectives = problem.objectives();
363        assert!(objectives.is_empty());
364
365        // Coloring has no weights
366        let weights: Vec<i32> = problem.weights();
367        assert!(weights.is_empty());
368
369        // is_weighted should return false
370        assert!(!problem.is_weighted());
371    }
372
373    #[test]
374    fn test_set_weights() {
375        let mut problem = Coloring::new(3, 2, vec![(0, 1)]);
376        // set_weights does nothing for Coloring
377        problem.set_weights(vec![1, 2, 3]);
378        assert!(!problem.is_weighted());
379    }
380}