1use crate::models::algebraic::QUBO;
16use crate::models::decision::Decision;
17use crate::models::formula::KSatisfiability;
18use crate::reduction;
19use crate::rules::traits::{ReduceTo, ReductionResult};
20use crate::variant::{K2, K3};
21#[derive(Debug, Clone)]
23pub struct ReductionKSatToQUBO {
24 target: Decision<QUBO<i64>>,
25 source_num_vars: usize,
26}
27
28impl ReductionResult for ReductionKSatToQUBO {
29 type Source = KSatisfiability<K2>;
30 type Target = Decision<QUBO<i64>>;
31
32 fn target_problem(&self) -> &Self::Target {
33 &self.target
34 }
35
36 fn extract_solution(
37 &self,
38 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
39 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
40 crate::rules::traits::validate_target_witness(
41 self.target_problem(),
42 target_solution,
43 |value| value.0,
44 "QUBO energy does not meet the SAT zero-penalty threshold",
45 )?;
46 Ok(target_solution[..self.source_num_vars].to_vec())
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct Reduction3SATToQUBO {
53 target: Decision<QUBO<i64>>,
54 source_num_vars: usize,
55}
56
57impl ReductionResult for Reduction3SATToQUBO {
58 type Source = KSatisfiability<K3>;
59 type Target = Decision<QUBO<i64>>;
60
61 fn target_problem(&self) -> &Self::Target {
62 &self.target
63 }
64
65 fn extract_solution(
66 &self,
67 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
68 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
69 crate::rules::traits::validate_target_witness(
70 self.target_problem(),
71 target_solution,
72 |value| value.0,
73 "QUBO energy does not meet the SAT zero-penalty threshold",
74 )?;
75 Ok(target_solution[..self.source_num_vars].to_vec())
76 }
77}
78
79fn add_coefficient(
84 matrix: &mut [Vec<i64>],
85 row: usize,
86 column: usize,
87 coefficient: i64,
88) -> Result<(), &'static str> {
89 matrix[row][column] = matrix[row][column]
90 .checked_add(coefficient)
91 .ok_or("adding a SAT QUBO coefficient")?;
92 Ok(())
93}
94
95fn add_2sat_clause_penalty(
96 matrix: &mut [Vec<i64>],
97 lits: &[(usize, bool)],
98) -> Result<(), &'static str> {
99 assert_eq!(lits.len(), 2, "Expected 2-literal clause");
100
101 let (var_i, neg_i) = lits[0];
102 let (var_j, neg_j) = lits[1];
103
104 let (i, j, ni, nj) = if var_i <= var_j {
106 (var_i, var_j, neg_i, neg_j)
107 } else {
108 (var_j, var_i, neg_j, neg_i)
109 };
110
111 match (ni, nj) {
112 (false, false) => {
113 add_coefficient(matrix, i, i, -1)?;
115 add_coefficient(matrix, j, j, -1)?;
116 add_coefficient(matrix, i, j, 1)?;
117 }
118 (true, false) => {
119 add_coefficient(matrix, i, i, 1)?;
121 add_coefficient(matrix, i, j, -1)?;
122 }
123 (false, true) => {
124 add_coefficient(matrix, j, j, 1)?;
126 add_coefficient(matrix, i, j, -1)?;
127 }
128 (true, true) => {
129 add_coefficient(matrix, i, j, 1)?;
131 }
132 }
133 Ok(())
134}
135
136fn add_3sat_clause_penalty(
151 matrix: &mut [Vec<i64>],
152 lits: &[(usize, bool)],
153 aux_var: usize,
154) -> Result<(), &'static str> {
155 assert_eq!(lits.len(), 3, "Expected 3-literal clause");
156 let penalty = 2; let (v1, n1) = lits[0];
159 let (v2, n2) = lits[1];
160 let (v3, n3) = lits[2];
161 let a = aux_var;
162
163 let add_yy = |matrix: &mut [Vec<i64>],
177 vi: usize,
178 ni: bool,
179 vj: usize,
180 nj: bool,
181 coeff: i64|
182 -> Result<(), &'static str> {
183 if vi == vj {
186 if ni == nj {
191 if ni {
193 add_coefficient(matrix, vi, vi, coeff)?;
195 } else {
196 add_coefficient(matrix, vi, vi, -coeff)?;
199 }
200 }
201 return Ok(());
203 }
204 let (lo, hi, lo_neg, hi_neg) = if vi < vj {
206 (vi, vj, ni, nj)
207 } else {
208 (vj, vi, nj, ni)
209 };
210 match (lo_neg, hi_neg) {
214 (true, true) => {
215 add_coefficient(matrix, lo, hi, coeff)?;
217 }
218 (true, false) => {
219 add_coefficient(matrix, lo, lo, coeff)?;
221 add_coefficient(matrix, lo, hi, -coeff)?;
222 }
223 (false, true) => {
224 add_coefficient(matrix, hi, hi, coeff)?;
226 add_coefficient(matrix, lo, hi, -coeff)?;
227 }
228 (false, false) => {
229 add_coefficient(matrix, lo, lo, -coeff)?;
232 add_coefficient(matrix, hi, hi, -coeff)?;
233 add_coefficient(matrix, lo, hi, coeff)?;
234 }
235 }
236 Ok(())
237 };
238
239 let add_ya = |matrix: &mut [Vec<i64>],
242 vi: usize,
243 ni: bool,
244 a: usize,
245 coeff: i64|
246 -> Result<(), &'static str> {
247 let (lo, hi) = if vi < a { (vi, a) } else { (a, vi) };
250 if ni {
251 add_coefficient(matrix, lo, hi, coeff)?;
253 } else {
254 add_coefficient(matrix, a, a, coeff)?;
256 add_coefficient(matrix, lo, hi, -coeff)?;
257 }
258 Ok(())
259 };
260
261 add_ya(matrix, v3, n3, a, 1)?;
263
264 add_yy(matrix, v1, n1, v2, n2, penalty)?;
266
267 add_ya(matrix, v1, n1, a, -2 * penalty)?;
269
270 add_ya(matrix, v2, n2, a, -2 * penalty)?;
272
273 add_coefficient(matrix, a, a, 3 * penalty)?;
276
277 Ok(())
278}
279
280fn build_qubo_matrix(
284 num_vars: usize,
285 clauses: &[crate::models::formula::CNFClause],
286 num_aux: usize,
287) -> Result<(Vec<Vec<i64>>, i64), &'static str> {
288 let total = num_vars
289 .checked_add(num_aux)
290 .ok_or("computing the number of SAT QUBO variables")?;
291 total
292 .checked_mul(total)
293 .ok_or("computing the SAT QUBO dense matrix entry count")?;
294 let mut matrix = vec![vec![0; total]; total];
295 let mut constant = 0i64;
296 for (idx, clause) in clauses.iter().enumerate() {
297 let literals: Vec<_> = clause
298 .variables()
299 .into_iter()
300 .zip(&clause.literals)
301 .map(|(variable, &literal)| (variable, literal < 0))
302 .collect();
303 let offset = match literals.as_slice() {
304 [] => 1,
305 &[(v, neg)] => {
306 add_coefficient(&mut matrix, v, v, if neg { 1 } else { -1 })?;
307 i64::from(!neg)
308 }
309 &[(_, n1), (_, n2)] => {
310 add_2sat_clause_penalty(&mut matrix, &literals)?;
311 i64::from(!n1 && !n2)
312 }
313 &[(_, n1), (_, n2), _] => {
314 add_3sat_clause_penalty(&mut matrix, &literals, num_vars + idx)?;
315 2 * i64::from(!n1 && !n2)
316 }
317 _ => unreachable!("the source validates clause width at most three"),
318 };
319 constant = constant
320 .checked_add(offset)
321 .ok_or("accumulating the SAT QUBO constant")?;
322 }
323 Ok((matrix, constant))
324}
325
326#[crate::aggregate_reduction(identity)]
327impl crate::rules::AggregateReductionResult for ReductionKSatToQUBO {}
328
329#[crate::aggregate_reduction(identity)]
330impl crate::rules::AggregateReductionResult for Reduction3SATToQUBO {}
331
332#[reduction(
333 transform = {
334 exact { num_vars = "num_vars" },
335 upper_bound { num_quadratic_terms = "num_vars * (num_vars - 1) / 2" },
336 }
337)]
338impl ReduceTo<Decision<QUBO<i64>>> for KSatisfiability<K2> {
339 type Result = ReductionKSatToQUBO;
340
341 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
342 let n = self.num_vars();
343 let (matrix, constant) =
344 build_qubo_matrix(n, self.clauses(), 0).map_err(|operation| {
345 crate::rules::ReductionError::integer_overflow::<
346 KSatisfiability<K2>,
347 Decision<QUBO<i64>>,
348 >(operation)
349 })?;
350
351 Ok(ReductionKSatToQUBO {
352 target: Decision::new(
353 QUBO::from_matrix(matrix).map_err(|message| {
354 crate::rules::ReductionError::construction::<
355 KSatisfiability<K2>,
356 Decision<QUBO<i64>>,
357 >(message)
358 })?,
359 -constant,
360 ),
361 source_num_vars: n,
362 })
363 }
364}
365
366#[reduction(transform = {
367 exact {
368 num_vars = "num_vars + num_clauses",
369 },
370 upper_bound {
371 num_quadratic_terms = "(num_vars + num_clauses) * ((num_vars + num_clauses) - 1) / 2",
372 },
373})]
374impl ReduceTo<Decision<QUBO<i64>>> for KSatisfiability<K3> {
375 type Result = Reduction3SATToQUBO;
376
377 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
378 let n = self.num_vars();
379 let (matrix, constant) =
380 build_qubo_matrix(n, self.clauses(), self.num_clauses()).map_err(|operation| {
381 crate::rules::ReductionError::integer_overflow::<
382 KSatisfiability<K3>,
383 Decision<QUBO<i64>>,
384 >(operation)
385 })?;
386
387 Ok(Reduction3SATToQUBO {
388 target: Decision::new(
389 QUBO::from_matrix(matrix).map_err(|message| {
390 crate::rules::ReductionError::construction::<
391 KSatisfiability<K3>,
392 Decision<QUBO<i64>>,
393 >(message)
394 })?,
395 -constant,
396 ),
397 source_num_vars: n,
398 })
399 }
400}
401
402#[cfg(feature = "example-db")]
403pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
404 use crate::export::SolutionPair;
405 use crate::models::algebraic::QUBO;
406 use crate::models::formula::CNFClause;
407
408 vec![
409 crate::example_db::specs::RuleExampleSpec {
410 id: "ksatisfiability_k2_to_qubo",
411 build: || {
412 let source = KSatisfiability::<K2>::new(
413 4,
414 vec![
415 CNFClause::new(vec![1, 2]),
416 CNFClause::new(vec![-1, 3]),
417 CNFClause::new(vec![-2, 4]),
418 CNFClause::new(vec![-3, -4]),
419 ],
420 );
421 crate::example_db::specs::rule_example_with_witness::<_, Decision<QUBO<i64>>>(
422 source,
423 SolutionPair {
424 source_config: serde_json::json!(vec![false, true, false, true]),
425 target_config: serde_json::json!(vec![false, true, false, true]),
426 },
427 )
428 },
429 },
430 crate::example_db::specs::RuleExampleSpec {
431 id: "ksatisfiability_to_qubo",
432 build: || {
433 let source = KSatisfiability::<K3>::new(
434 5,
435 vec![
436 CNFClause::new(vec![1, 2, -3]),
437 CNFClause::new(vec![-1, 3, 4]),
438 CNFClause::new(vec![2, -4, 5]),
439 CNFClause::new(vec![-2, 3, -5]),
440 CNFClause::new(vec![1, -3, 5]),
441 CNFClause::new(vec![-1, -2, 4]),
442 CNFClause::new(vec![3, -4, -5]),
443 ],
444 );
445 crate::example_db::specs::rule_example_with_witness::<_, Decision<QUBO<i64>>>(
446 source,
447 SolutionPair {
448 source_config: serde_json::json!(vec![false, false, false, false, false]),
449 target_config: serde_json::json!(vec![
450 false, false, false, false, false, true, false, false, false, false,
451 false, false
452 ]),
453 },
454 )
455 },
456 },
457 ]
458}
459
460#[cfg(test)]
461#[path = "../unit_tests/rules/ksatisfiability_qubo.rs"]
462mod tests;