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