problemreductions/models/graph/
kcoloring.rs

1//! Graph K-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::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::{Problem, SatisfactionProblem};
9use crate::variant::{KValue, VariantParam, K2, K3, K4, K5, KN};
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "KColoring",
15        module_path: module_path!(),
16        description: "Find valid k-coloring of a graph",
17        fields: &[
18            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
19        ],
20    }
21}
22
23/// The Graph K-Coloring problem.
24///
25/// Given a graph G = (V, E) and K colors, find an assignment of colors
26/// to vertices such that no two adjacent vertices have the same color.
27///
28/// # Type Parameters
29///
30/// * `K` - KValue type representing the number of colors (e.g., K3 for 3-coloring)
31/// * `G` - Graph type (e.g., SimpleGraph, KingsSubgraph)
32///
33/// # Example
34///
35/// ```
36/// use problemreductions::models::graph::KColoring;
37/// use problemreductions::topology::SimpleGraph;
38/// use problemreductions::variant::K3;
39/// use problemreductions::{Problem, Solver, BruteForce};
40///
41/// // Triangle graph needs at least 3 colors
42/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]);
43/// let problem = KColoring::<K3, _>::new(graph);
44///
45/// let solver = BruteForce::new();
46/// let solutions = solver.find_all_satisfying(&problem);
47///
48/// // Verify all solutions are valid colorings
49/// for sol in &solutions {
50///     assert!(problem.evaluate(sol));
51/// }
52/// ```
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
55pub struct KColoring<K: KValue, G> {
56    /// The underlying graph.
57    graph: G,
58    /// Runtime number of colors. Always set; for compile-time K types it equals K::K.
59    #[serde(default = "default_num_colors::<K>")]
60    num_colors: usize,
61    #[serde(skip)]
62    _phantom: std::marker::PhantomData<K>,
63}
64
65fn default_num_colors<K: KValue>() -> usize {
66    K::K.unwrap_or(0)
67}
68
69impl<K: KValue, G: Graph> KColoring<K, G> {
70    /// Create a new K-Coloring problem from a graph.
71    ///
72    /// # Panics
73    /// Panics if `K` is `KN` (use [`KColoring::<KN, G>::with_k`] instead).
74    pub fn new(graph: G) -> Self {
75        Self {
76            graph,
77            num_colors: K::K.expect("KN requires with_k"),
78            _phantom: std::marker::PhantomData,
79        }
80    }
81
82    /// Get a reference to the underlying graph.
83    pub fn graph(&self) -> &G {
84        &self.graph
85    }
86
87    /// Get the number of colors.
88    pub fn num_colors(&self) -> usize {
89        self.num_colors
90    }
91
92    /// Check if a configuration is a valid coloring.
93    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
94        is_valid_coloring(&self.graph, config, self.num_colors)
95    }
96
97    /// Check if a coloring is valid.
98    fn is_valid_coloring(&self, config: &[usize]) -> bool {
99        for (u, v) in self.graph.edges() {
100            let color_u = config.get(u).copied().unwrap_or(0);
101            let color_v = config.get(v).copied().unwrap_or(0);
102            if color_u == color_v {
103                return false;
104            }
105        }
106        true
107    }
108}
109
110impl<G: Graph> KColoring<KN, G> {
111    /// Create a K-Coloring problem with an explicit number of colors.
112    ///
113    /// Only available for `KN` (runtime K). For compile-time K types like
114    /// `K3`, use [`new`](KColoring::new) which derives K from the type
115    /// parameter.
116    pub fn with_k(graph: G, num_colors: usize) -> Self {
117        Self {
118            graph,
119            num_colors,
120            _phantom: std::marker::PhantomData,
121        }
122    }
123}
124
125impl<K: KValue, G: Graph> KColoring<K, G> {
126    /// Get the number of vertices in the underlying graph.
127    pub fn num_vertices(&self) -> usize {
128        self.graph().num_vertices()
129    }
130
131    /// Get the number of edges in the underlying graph.
132    pub fn num_edges(&self) -> usize {
133        self.graph().num_edges()
134    }
135}
136
137impl<K: KValue, G> Problem for KColoring<K, G>
138where
139    G: Graph + VariantParam,
140{
141    const NAME: &'static str = "KColoring";
142    type Metric = bool;
143
144    fn variant() -> Vec<(&'static str, &'static str)> {
145        crate::variant_params![K, G]
146    }
147
148    fn dims(&self) -> Vec<usize> {
149        vec![self.num_colors; self.graph.num_vertices()]
150    }
151
152    fn evaluate(&self, config: &[usize]) -> bool {
153        self.is_valid_coloring(config)
154    }
155}
156
157impl<K: KValue, G: Graph + VariantParam> SatisfactionProblem for KColoring<K, G> {}
158
159/// Check if a coloring is valid for a graph.
160///
161/// # Panics
162/// Panics if `coloring.len() != graph.num_vertices()`.
163pub(crate) fn is_valid_coloring<G: Graph>(
164    graph: &G,
165    coloring: &[usize],
166    num_colors: usize,
167) -> bool {
168    assert_eq!(
169        coloring.len(),
170        graph.num_vertices(),
171        "coloring length must match num_vertices"
172    );
173    // Check all colors are valid
174    if coloring.iter().any(|&c| c >= num_colors) {
175        return false;
176    }
177    // Check no adjacent vertices have same color
178    for (u, v) in graph.edges() {
179        if coloring[u] == coloring[v] {
180            return false;
181        }
182    }
183    true
184}
185
186crate::declare_variants! {
187    KColoring<KN, SimpleGraph> => "2^num_vertices",
188    KColoring<K2, SimpleGraph> => "num_vertices + num_edges",
189    KColoring<K3, SimpleGraph> => "1.3289^num_vertices",
190    KColoring<K4, SimpleGraph> => "1.7159^num_vertices",
191    // Best known: O*((2-ε)^n) for some ε > 0 (Zamir 2021), concrete ε unknown
192    KColoring<K5, SimpleGraph> => "2^num_vertices",
193}
194
195#[cfg(test)]
196#[path = "../../unit_tests/models/graph/kcoloring.rs"]
197mod tests;