1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::variant::{KValue, VariantParam, K1, K2, K3, K4, K5, KN};
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "KColoring",
15 display_name: "K-Coloring",
16 aliases: &[],
17 dimensions: &[
18 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
19 VariantDimension::new("k", "KN", &["KN", "K1", "K2", "K3", "K4", "K5"]),
20 ],
21 category: crate::registry::ProblemCategory::Graph,
22 module_path: module_path!(),
23 description: "Find valid k-coloring of a graph",
24 fields: RuntimeKColoringCreateSpec::FIELDS,
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
60pub struct KColoring<K: KValue, G> {
61 graph: G,
63 #[serde(
65 default = "default_num_colors::<K>",
66 deserialize_with = "deserialize_num_colors::<K, _>"
67 )]
68 num_colors: usize,
69 #[serde(skip)]
70 _phantom: std::marker::PhantomData<K>,
71}
72
73#[derive(Debug, Deserialize, crate::CreateSpec)]
74struct FixedKColoringCreateSpec {
75 #[create(codec = "edge-list")]
77 graph: Vec<(usize, usize)>,
78 num_vertices: Option<usize>,
80}
81
82#[derive(Debug, Deserialize, crate::CreateSpec)]
83struct RuntimeKColoringCreateSpec {
84 #[create(codec = "edge-list")]
86 graph: Vec<(usize, usize)>,
87 num_vertices: Option<usize>,
89 k: usize,
91}
92
93fn simple_graph_from_create(
94 edges: Vec<(usize, usize)>,
95 num_vertices: Option<usize>,
96) -> Result<SimpleGraph, crate::registry::ConstructionError> {
97 if edges.is_empty() && num_vertices.is_none() {
98 return Err("num_vertices is required for an empty graph"
99 .to_string()
100 .into());
101 }
102 for (index, &(u, v)) in edges.iter().enumerate() {
103 if u == v {
104 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
105 }
106 }
107 let inferred = edges
108 .iter()
109 .flat_map(|&(u, v)| [u, v])
110 .max()
111 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
112 .transpose()?
113 .unwrap_or(0);
114 let count = num_vertices.unwrap_or(inferred);
115 if count < inferred {
116 return Err(format!(
117 "num_vertices {count} is too small for graph endpoints; need at least {inferred}"
118 )
119 .into());
120 }
121 Ok(SimpleGraph::new(count, edges))
122}
123
124impl<K: KValue> TryFrom<FixedKColoringCreateSpec> for KColoring<K, SimpleGraph> {
125 type Error = crate::registry::ConstructionError;
126
127 fn try_from(spec: FixedKColoringCreateSpec) -> Result<Self, Self::Error> {
128 let num_colors = K::K.ok_or("runtime KColoring requires k")?;
129 Ok(Self {
130 graph: simple_graph_from_create(spec.graph, spec.num_vertices)?,
131 num_colors,
132 _phantom: std::marker::PhantomData,
133 })
134 }
135}
136
137impl TryFrom<RuntimeKColoringCreateSpec> for KColoring<KN, SimpleGraph> {
138 type Error = crate::registry::ConstructionError;
139
140 fn try_from(spec: RuntimeKColoringCreateSpec) -> Result<Self, Self::Error> {
141 if spec.k == 0 {
142 return Err("k must be positive".to_string().into());
143 }
144 Ok(Self::with_k(
145 simple_graph_from_create(spec.graph, spec.num_vertices)?,
146 spec.k,
147 ))
148 }
149}
150
151fn deserialize_num_colors<'de, K: KValue, D: serde::Deserializer<'de>>(
153 deserializer: D,
154) -> Result<usize, D::Error> {
155 let num_colors = usize::deserialize(deserializer)?;
156 match K::K {
157 Some(fixed) if num_colors != fixed => Err(serde::de::Error::custom(format!(
158 "fixed K requires {fixed} colors, got {num_colors}"
159 ))),
160 _ => Ok(num_colors),
161 }
162}
163
164fn default_num_colors<K: KValue>() -> usize {
165 K::K.unwrap_or(0)
166}
167
168impl<K: KValue, G: Graph> KColoring<K, G> {
169 pub fn new(graph: G) -> Self {
174 Self {
175 graph,
176 num_colors: K::K.expect("KN requires with_k"),
177 _phantom: std::marker::PhantomData,
178 }
179 }
180
181 pub fn graph(&self) -> &G {
183 &self.graph
184 }
185
186 pub fn num_colors(&self) -> usize {
188 self.num_colors
189 }
190
191 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
193 is_valid_coloring(&self.graph, config, self.num_colors)
194 }
195
196 fn is_valid_coloring(&self, config: &[usize]) -> bool {
198 for (u, v) in self.graph.edges() {
199 let color_u = config.get(u).copied().unwrap_or(0);
200 let color_v = config.get(v).copied().unwrap_or(0);
201 if color_u == color_v {
202 return false;
203 }
204 }
205 true
206 }
207}
208
209impl<G: Graph> KColoring<KN, G> {
210 pub fn with_k(graph: G, num_colors: usize) -> Self {
216 Self {
217 graph,
218 num_colors,
219 _phantom: std::marker::PhantomData,
220 }
221 }
222}
223
224impl<K: KValue, G: Graph> KColoring<K, G> {
225 pub fn num_vertices(&self) -> usize {
227 self.graph().num_vertices()
228 }
229
230 pub fn num_edges(&self) -> usize {
232 self.graph().num_edges()
233 }
234}
235
236impl<K: KValue, G> Problem for KColoring<K, G>
237where
238 G: Graph + VariantParam,
239{
240 const NAME: &'static str = "KColoring";
241 type Solution = Vec<usize>;
242 type Value = crate::types::Or;
243
244 crate::problem_parameters![
245 ("num_edges", num_edges),
246 ("num_vertices", num_vertices),
247 ("num_colors", num_colors),
248 ];
249
250 fn variant() -> Vec<(&'static str, &'static str)> {
251 crate::variant_params![K, G]
252 }
253
254 fn evaluate(
255 &self,
256 config: &Self::Solution,
257 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
258 if config.len() != self.graph.num_vertices() {
259 return Err(crate::traits::EvaluationError::InvalidConfiguration(
260 "color assignment length does not match the graph vertices".into(),
261 ));
262 }
263 if config.iter().any(|&color| color >= self.num_colors) {
264 return Err(crate::traits::EvaluationError::InvalidConfiguration(
265 "color assignment contains an out-of-range color".into(),
266 ));
267 }
268 Ok(crate::types::Or(self.is_valid_coloring(config)))
269 }
270}
271
272impl<K: KValue, G> crate::solvers::BruteForceProblem for KColoring<K, G>
273where
274 G: Graph + VariantParam,
275{
276 fn dimensions(&self) -> Vec<usize> {
277 vec![self.num_colors; self.graph.num_vertices()]
278 }
279}
280
281pub(crate) fn is_valid_coloring<G: Graph>(
286 graph: &G,
287 coloring: &[usize],
288 num_colors: usize,
289) -> bool {
290 assert_eq!(
291 coloring.len(),
292 graph.num_vertices(),
293 "coloring length must match num_vertices"
294 );
295 if coloring.iter().any(|&c| c >= num_colors) {
297 return false;
298 }
299 for (u, v) in graph.edges() {
301 if coloring[u] == coloring[v] {
302 return false;
303 }
304 }
305 true
306}
307
308#[cfg(feature = "example-db")]
309pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
310 vec![crate::example_db::specs::ModelExampleSpec {
311 id: "kcoloring_k3_simplegraph",
312 instance: Box::new(KColoring::<K3, _>::new(SimpleGraph::new(
313 5,
314 vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
315 ))),
316 optimal_config: serde_json::json!(vec![0, 1, 1, 0, 2]),
317 optimal_value: serde_json::json!(true),
318 }]
319}
320
321crate::impl_random_generate!(KColoring<KN, SimpleGraph>, crate::random::ColoringRandomSpec, |spec| {
322 let k = spec.k.unwrap_or(3);
323 if k == 0 {
324 return Err("k must be positive".to_string().into());
325 }
326 Ok(KColoring::with_k(spec.graph()?, k))
327});
328crate::impl_random_generate!(KColoring<K2, SimpleGraph>, crate::random::ColoringRandomSpec, |spec| {
329 if spec.k.is_some_and(|k| k != 2) { return Err("k must match the selected K2 variant".to_string().into()); }
330 Ok(KColoring::new(spec.graph()?))
331});
332crate::impl_random_generate!(KColoring<K3, SimpleGraph>, crate::random::ColoringRandomSpec, |spec| {
333 if spec.k.is_some_and(|k| k != 3) { return Err("k must match the selected K3 variant".to_string().into()); }
334 Ok(KColoring::new(spec.graph()?))
335});
336crate::impl_random_generate!(KColoring<K4, SimpleGraph>, crate::random::ColoringRandomSpec, |spec| {
337 if spec.k.is_some_and(|k| k != 4) { return Err("k must match the selected K4 variant".to_string().into()); }
338 Ok(KColoring::new(spec.graph()?))
339});
340crate::impl_random_generate!(KColoring<K5, SimpleGraph>, crate::random::ColoringRandomSpec, |spec| {
341 if spec.k.is_some_and(|k| k != 5) { return Err("k must match the selected K5 variant".to_string().into()); }
342 Ok(KColoring::new(spec.graph()?))
343});
344
345crate::declare_variants! {
346 default KColoring<KN, SimpleGraph> => "2^num_vertices" create RuntimeKColoringCreateSpec random,
347 KColoring<K1, SimpleGraph> => "num_vertices + num_edges" create FixedKColoringCreateSpec,
348 KColoring<K2, SimpleGraph> => "num_vertices + num_edges" create FixedKColoringCreateSpec random,
349 KColoring<K3, SimpleGraph> => "1.3289^num_vertices" create FixedKColoringCreateSpec random,
350 KColoring<K4, SimpleGraph> => "1.7159^num_vertices" create FixedKColoringCreateSpec random,
351 KColoring<K5, SimpleGraph> => "2^num_vertices" create FixedKColoringCreateSpec random,
353}
354
355crate::register_brute_force! {
356 KColoring<KN, SimpleGraph>,
357 KColoring<K1, SimpleGraph>,
358 KColoring<K2, SimpleGraph>,
359 KColoring<K3, SimpleGraph>,
360 KColoring<K4, SimpleGraph>,
361 KColoring<K5, SimpleGraph>,
362}
363
364#[cfg(test)]
365#[path = "../../unit_tests/models/graph/kcoloring.rs"]
366mod tests;