1use crate::models::algebraic::QUBO;
12use crate::models::decision::Decision;
13use crate::models::graph::KColoring;
14use crate::reduction;
15use crate::rules::traits::{ReduceTo, ReductionResult};
16use crate::topology::{Graph, SimpleGraph};
17use crate::variant::{KValue, K2, K3, KN};
18
19#[derive(Debug, Clone)]
21pub struct ReductionKColoringToQUBO<K: KValue> {
22 target: Decision<QUBO<i64>>,
23 num_vertices: usize,
24 num_colors: usize,
25 _phantom: std::marker::PhantomData<K>,
26}
27
28impl<K: KValue> ReductionResult for ReductionKColoringToQUBO<K> {
29 type Source = KColoring<K, SimpleGraph>;
30 type Target = Decision<QUBO<i64>>;
31
32 fn target_problem(&self) -> &Self::Target {
33 &self.target
34 }
35
36 fn extract_solution(
38 &self,
39 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
40 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
41 crate::rules::traits::validate_target_witness(
42 self.target_problem(),
43 target_solution,
44 |value| value.0,
45 "target QUBO configuration does not certify a proper coloring",
46 )?;
47
48 (0..self.num_vertices)
49 .map(|vertex| {
50 let mut selected = (0..self.num_colors)
51 .filter(|&color| target_solution[vertex * self.num_colors + color]);
52 match (selected.next(), selected.next()) {
53 (Some(color), None) => Ok(color),
54 (None, _) => Err(crate::rules::ExtractionError::invalid(format!(
55 "assignment row {vertex} has no selected color"
56 ))),
57 (Some(_), Some(_)) => Err(crate::rules::ExtractionError::invalid(format!(
58 "assignment row {vertex} has multiple selected colors"
59 ))),
60 }
61 })
62 .collect()
63 }
64}
65
66crate::register_aggregate_reduction!(ReductionKColoringToQUBO<KN>);
67
68#[crate::aggregate_reduction(identity)]
69impl<K: KValue> crate::rules::AggregateReductionResult for ReductionKColoringToQUBO<K> {}
70
71fn coloring_qubo_parameters<K: KValue>(
73 n: usize,
74 k: usize,
75) -> Result<(usize, i64, i64), crate::rules::ReductionError> {
76 let overflow = |operation| {
77 crate::rules::ReductionError::integer_overflow::<
78 KColoring<K, SimpleGraph>,
79 Decision<QUBO<i64>>,
80 >(operation)
81 };
82 let nq = n
83 .checked_mul(k)
84 .ok_or_else(|| overflow("computing the number of QUBO variables"))?;
85 nq.checked_mul(nq)
86 .ok_or_else(|| overflow("computing the QUBO matrix size"))?;
87 let n_i64 = i64::try_from(n)
88 .map_err(|_| overflow("converting the vertex count to a QUBO coefficient"))?;
89 let penalty = n_i64
90 .checked_add(1)
91 .ok_or_else(|| overflow("computing the coloring penalty"))?;
92 let feasible_energy = n_i64
93 .checked_mul(penalty)
94 .and_then(|value| value.checked_mul(-2))
95 .ok_or_else(|| overflow("computing the zero-penalty coloring energy"))?;
96 Ok((nq, penalty, feasible_energy))
97}
98
99fn reduce_kcoloring_to_qubo<K: KValue>(
101 problem: &KColoring<K, SimpleGraph>,
102) -> Result<ReductionKColoringToQUBO<K>, crate::rules::ReductionError> {
103 let k = problem.num_colors();
104 let n = problem.graph().num_vertices();
105 let edges = problem.graph().edges();
106 let overflow = |operation| {
107 crate::rules::ReductionError::integer_overflow::<
108 KColoring<K, SimpleGraph>,
109 Decision<QUBO<i64>>,
110 >(operation)
111 };
112 let (nq, penalty, feasible_energy) = coloring_qubo_parameters::<K>(n, k)?;
113
114 let diagonal_penalty = penalty
115 .checked_mul(-2)
116 .ok_or_else(|| overflow("computing a coloring diagonal coefficient"))?;
117 let one_hot_interaction = penalty
118 .checked_mul(4)
119 .ok_or_else(|| overflow("computing a one-hot interaction coefficient"))?;
120
121 let mut matrix = vec![vec![0i64; nq]; nq];
122
123 for v in 0..n {
129 for c in 0..k {
130 let idx = v * k + c;
131 matrix[idx][idx] = matrix[idx][idx]
133 .checked_add(diagonal_penalty)
134 .ok_or_else(|| overflow("adding a coloring diagonal coefficient"))?;
135 }
136 for c1 in 0..k {
138 for c2 in (c1 + 1)..k {
139 let idx1 = v * k + c1;
140 let idx2 = v * k + c2;
141 matrix[idx1][idx2] = matrix[idx1][idx2]
142 .checked_add(one_hot_interaction)
143 .ok_or_else(|| overflow("adding a one-hot interaction coefficient"))?;
144 }
145 }
146 }
147
148 for (u, v) in &edges {
150 for c in 0..k {
151 let idx_u = u * k + c;
152 let idx_v = v * k + c;
153 let (i, j) = if idx_u < idx_v {
154 (idx_u, idx_v)
155 } else {
156 (idx_v, idx_u)
157 };
158 matrix[i][j] = matrix[i][j]
159 .checked_add(penalty)
160 .ok_or_else(|| overflow("adding an edge-conflict coefficient"))?;
161 }
162 }
163
164 Ok(ReductionKColoringToQUBO {
165 target: Decision::new(
166 QUBO::from_matrix(matrix).map_err(|message| {
167 crate::rules::ReductionError::construction::<
168 KColoring<K, SimpleGraph>,
169 Decision<QUBO<i64>>,
170 >(message)
171 })?,
172 feasible_energy,
173 ),
174 num_vertices: n,
175 num_colors: k,
176
177 _phantom: std::marker::PhantomData,
178 })
179}
180
181#[reduction(
185 transform = {
186 exact {
187 num_vars = "num_vertices * num_colors",
188 },
189 upper_bound {
190 num_quadratic_terms = "num_vertices * num_colors * (num_colors - 1) / 2 + num_edges * num_colors",
191 },
192 }
193)]
194impl ReduceTo<Decision<QUBO<i64>>> for KColoring<KN, SimpleGraph> {
195 type Result = ReductionKColoringToQUBO<KN>;
196
197 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
198 reduce_kcoloring_to_qubo(self)
199 }
200}
201
202macro_rules! impl_kcoloring_to_qubo {
204 ($($ktype:ty),+) => {$(
205 impl ReduceTo<Decision<QUBO<i64>>> for KColoring<$ktype, SimpleGraph> {
206 type Result = ReductionKColoringToQUBO<$ktype>;
207 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
208 reduce_kcoloring_to_qubo(self)
209 }
210 }
211 )+};
212}
213
214impl_kcoloring_to_qubo!(K2, K3);
215
216#[cfg(feature = "example-db")]
217pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
218 use crate::export::SolutionPair;
219 use crate::models::algebraic::QUBO;
220
221 vec![crate::example_db::specs::RuleExampleSpec {
222 id: "kcoloring_to_qubo",
223 build: || {
224 let (n, edges) = crate::topology::small_graphs::house();
225 let source = KColoring::<KN, _>::with_k(SimpleGraph::new(n, edges), 3);
226 crate::example_db::specs::rule_example_with_witness::<_, Decision<QUBO<i64>>>(
227 source,
228 SolutionPair {
229 source_config: serde_json::json!(vec![1, 2, 2, 1, 0]),
230 target_config: serde_json::json!(vec![
231 false, true, false, false, false, true, false, false, true, false, true,
232 false, true, false, false
233 ]),
234 },
235 )
236 },
237 }]
238}
239
240#[cfg(test)]
241#[path = "../unit_tests/rules/coloring_qubo.rs"]
242mod tests;