1use crate::models::satisfiability::{CNFClause, KSatisfiability, Satisfiability};
10use crate::rules::traits::{ReduceTo, ReductionResult};
11use crate::traits::Problem;
12use crate::types::ProblemSize;
13use num_traits::{Num, Zero};
14use std::ops::AddAssign;
15
16#[derive(Debug, Clone)]
21pub struct ReductionSATToKSAT<const K: usize, W> {
22 source_num_vars: usize,
24 target: KSatisfiability<K, W>,
26 source_size: ProblemSize,
28}
29
30impl<const K: usize, W> ReductionResult for ReductionSATToKSAT<K, W>
31where
32 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
33{
34 type Source = Satisfiability<W>;
35 type Target = KSatisfiability<K, W>;
36
37 fn target_problem(&self) -> &Self::Target {
38 &self.target
39 }
40
41 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
42 target_solution[..self.source_num_vars].to_vec()
44 }
45
46 fn source_size(&self) -> ProblemSize {
47 self.source_size.clone()
48 }
49
50 fn target_size(&self) -> ProblemSize {
51 self.target.problem_size()
52 }
53}
54
55fn add_clause_to_ksat(
71 k: usize,
72 clause: &CNFClause,
73 result_clauses: &mut Vec<CNFClause>,
74 mut next_var: i32,
75) -> i32 {
76 let len = clause.len();
77
78 if len == k {
79 result_clauses.push(clause.clone());
81 } else if len < k {
82 let ancilla = next_var;
86 next_var += 1;
87
88 let mut lits_pos = clause.literals.clone();
90 lits_pos.push(ancilla);
91 next_var = add_clause_to_ksat(k, &CNFClause::new(lits_pos), result_clauses, next_var);
92
93 let mut lits_neg = clause.literals.clone();
95 lits_neg.push(-ancilla);
96 next_var = add_clause_to_ksat(k, &CNFClause::new(lits_neg), result_clauses, next_var);
97 } else {
98 assert!(k >= 3, "K must be at least 3 for splitting");
101
102 let ancilla = next_var;
103 next_var += 1;
104
105 let mut first_lits: Vec<i32> = clause.literals[..k - 1].to_vec();
107 first_lits.push(ancilla);
108 result_clauses.push(CNFClause::new(first_lits));
109
110 let mut remaining_lits = vec![-ancilla];
112 remaining_lits.extend_from_slice(&clause.literals[k - 1..]);
113 let remaining_clause = CNFClause::new(remaining_lits);
114
115 next_var = add_clause_to_ksat(k, &remaining_clause, result_clauses, next_var);
117 }
118
119 next_var
120}
121
122macro_rules! impl_sat_to_ksat {
128 ($k:expr) => {
129 impl<W> ReduceTo<KSatisfiability<$k, W>> for Satisfiability<W>
130 where
131 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
132 {
133 type Result = ReductionSATToKSAT<$k, W>;
134
135 fn reduce_to(&self) -> Self::Result {
136 let source_num_vars = self.num_vars();
137 let mut result_clauses = Vec::new();
138 let mut next_var = (source_num_vars + 1) as i32; for clause in self.clauses() {
141 next_var =
142 add_clause_to_ksat($k, clause, &mut result_clauses, next_var);
143 }
144
145 let total_vars = (next_var - 1) as usize;
147
148 let target = KSatisfiability::<$k, W>::new(total_vars, result_clauses);
149
150 ReductionSATToKSAT {
151 source_num_vars,
152 target,
153 source_size: self.problem_size(),
154 }
155 }
156 }
157 };
158}
159
160impl_sat_to_ksat!(3);
162impl_sat_to_ksat!(4);
163impl_sat_to_ksat!(5);
164
165#[derive(Debug, Clone)]
169pub struct ReductionKSATToSAT<const K: usize, W> {
170 target: Satisfiability<W>,
172 source_size: ProblemSize,
174}
175
176impl<const K: usize, W> ReductionResult for ReductionKSATToSAT<K, W>
177where
178 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
179{
180 type Source = KSatisfiability<K, W>;
181 type Target = Satisfiability<W>;
182
183 fn target_problem(&self) -> &Self::Target {
184 &self.target
185 }
186
187 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
188 target_solution.to_vec()
190 }
191
192 fn source_size(&self) -> ProblemSize {
193 self.source_size.clone()
194 }
195
196 fn target_size(&self) -> ProblemSize {
197 self.target.problem_size()
198 }
199}
200
201impl<const K: usize, W> ReduceTo<Satisfiability<W>> for KSatisfiability<K, W>
202where
203 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
204{
205 type Result = ReductionKSATToSAT<K, W>;
206
207 fn reduce_to(&self) -> Self::Result {
208 let clauses = self.clauses().to_vec();
209 let target = Satisfiability::new(self.num_vars(), clauses);
210
211 ReductionKSATToSAT {
212 target,
213 source_size: self.problem_size(),
214 }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::solvers::{BruteForce, Solver};
222
223 #[test]
224 fn test_sat_to_3sat_exact_size() {
225 let sat = Satisfiability::<i32>::new(3, vec![CNFClause::new(vec![1, 2, 3])]);
227
228 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
229 let ksat = reduction.target_problem();
230
231 assert_eq!(ksat.num_vars(), 3);
232 assert_eq!(ksat.num_clauses(), 1);
233 assert_eq!(ksat.clauses()[0].literals, vec![1, 2, 3]);
234 }
235
236 #[test]
237 fn test_sat_to_3sat_padding() {
238 let sat = Satisfiability::<i32>::new(2, vec![CNFClause::new(vec![1, 2])]);
241
242 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
243 let ksat = reduction.target_problem();
244
245 assert_eq!(ksat.num_clauses(), 2);
247 for clause in ksat.clauses() {
249 assert_eq!(clause.len(), 3);
250 }
251 }
252
253 #[test]
254 fn test_sat_to_3sat_splitting() {
255 let sat = Satisfiability::<i32>::new(4, vec![CNFClause::new(vec![1, 2, 3, 4])]);
258
259 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
260 let ksat = reduction.target_problem();
261
262 assert_eq!(ksat.num_clauses(), 2);
264 for clause in ksat.clauses() {
266 assert_eq!(clause.len(), 3);
267 }
268
269 let c1 = &ksat.clauses()[0];
271 let c2 = &ksat.clauses()[1];
272 assert_eq!(c1.literals[0], 1);
274 assert_eq!(c1.literals[1], 2);
275 let ancilla = c1.literals[2];
276 assert!(ancilla > 0);
277 assert_eq!(c2.literals[0], -ancilla);
279 assert_eq!(c2.literals[1], 3);
280 assert_eq!(c2.literals[2], 4);
281 }
282
283 #[test]
284 fn test_sat_to_3sat_large_clause() {
285 let sat = Satisfiability::<i32>::new(5, vec![CNFClause::new(vec![1, 2, 3, 4, 5])]);
288
289 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
290 let ksat = reduction.target_problem();
291
292 assert_eq!(ksat.num_clauses(), 3);
294 for clause in ksat.clauses() {
296 assert_eq!(clause.len(), 3);
297 }
298 }
299
300 #[test]
301 fn test_sat_to_3sat_single_literal() {
302 let sat = Satisfiability::<i32>::new(1, vec![CNFClause::new(vec![1])]);
305
306 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
307 let ksat = reduction.target_problem();
308
309 assert_eq!(ksat.num_clauses(), 4);
315 for clause in ksat.clauses() {
316 assert_eq!(clause.len(), 3);
317 }
318 }
319
320 #[test]
321 fn test_sat_to_3sat_preserves_satisfiability() {
322 let sat = Satisfiability::<i32>::new(
324 3,
325 vec![
326 CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 2, 3]), CNFClause::new(vec![1, -2, 3, -3]), ],
330 );
331
332 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
333 let ksat = reduction.target_problem();
334
335 let solver = BruteForce::new();
337
338 let sat_solutions = solver.find_best(&sat);
339 let ksat_solutions = solver.find_best(ksat);
340
341 let sat_satisfiable = sat_solutions.iter().any(|s| sat.solution_size(s).is_valid);
343 let ksat_satisfiable = ksat_solutions.iter().any(|s| ksat.solution_size(s).is_valid);
344
345 assert_eq!(sat_satisfiable, ksat_satisfiable);
346
347 if ksat_satisfiable {
349 for ksat_sol in &ksat_solutions {
350 if ksat.solution_size(ksat_sol).is_valid {
351 let sat_sol = reduction.extract_solution(ksat_sol);
352 assert_eq!(sat_sol.len(), 3); }
354 }
355 }
356 }
357
358 #[test]
359 fn test_sat_to_3sat_solution_extraction() {
360 let sat = Satisfiability::<i32>::new(2, vec![CNFClause::new(vec![1, 2])]);
361
362 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
363 let ksat = reduction.target_problem();
364
365 let solver = BruteForce::new();
367 let ksat_solutions = solver.find_best(ksat);
368
369 for ksat_sol in &ksat_solutions {
371 if ksat.solution_size(ksat_sol).is_valid {
372 let sat_sol = reduction.extract_solution(ksat_sol);
373 assert_eq!(sat_sol.len(), 2);
375 assert!(sat.solution_size(&sat_sol).is_valid);
377 }
378 }
379 }
380
381 #[test]
382 fn test_3sat_to_sat() {
383 let ksat = KSatisfiability::<3, i32>::new(
384 3,
385 vec![
386 CNFClause::new(vec![1, 2, 3]),
387 CNFClause::new(vec![-1, -2, 3]),
388 ],
389 );
390
391 let reduction = ReduceTo::<Satisfiability<i32>>::reduce_to(&ksat);
392 let sat = reduction.target_problem();
393
394 assert_eq!(sat.num_vars(), 3);
395 assert_eq!(sat.num_clauses(), 2);
396
397 assert_eq!(sat.clauses()[0].literals, vec![1, 2, 3]);
399 assert_eq!(sat.clauses()[1].literals, vec![-1, -2, 3]);
400 }
401
402 #[test]
403 fn test_3sat_to_sat_solution_extraction() {
404 let ksat = KSatisfiability::<3, i32>::new(3, vec![CNFClause::new(vec![1, 2, 3])]);
405
406 let reduction = ReduceTo::<Satisfiability<i32>>::reduce_to(&ksat);
407
408 let sol = vec![1, 0, 1];
409 let extracted = reduction.extract_solution(&sol);
410 assert_eq!(extracted, vec![1, 0, 1]);
411 }
412
413 #[test]
414 fn test_roundtrip_sat_3sat_sat() {
415 let original_sat = Satisfiability::<i32>::new(
417 3,
418 vec![CNFClause::new(vec![1, -2]), CNFClause::new(vec![2, 3])],
419 );
420
421 let to_ksat = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&original_sat);
423 let ksat = to_ksat.target_problem();
424
425 let to_sat = ReduceTo::<Satisfiability<i32>>::reduce_to(ksat);
427 let final_sat = to_sat.target_problem();
428
429 let solver = BruteForce::new();
431
432 let orig_solutions = solver.find_best(&original_sat);
433 let ksat_solutions = solver.find_best(ksat);
434 let final_solutions = solver.find_best(final_sat);
435
436 assert!(orig_solutions.iter().any(|s| original_sat.solution_size(s).is_valid));
438 assert!(ksat_solutions.iter().any(|s| ksat.solution_size(s).is_valid));
439 assert!(final_solutions.iter().any(|s| final_sat.solution_size(s).is_valid));
440 }
441
442 #[test]
443 fn test_sat_to_4sat() {
444 let sat = Satisfiability::<i32>::new(
445 4,
446 vec![
447 CNFClause::new(vec![1, 2]), CNFClause::new(vec![1, 2, 3, 4]), CNFClause::new(vec![1, 2, 3, 4, -1]), ],
451 );
452
453 let reduction = ReduceTo::<KSatisfiability<4, i32>>::reduce_to(&sat);
454 let ksat = reduction.target_problem();
455
456 for clause in ksat.clauses() {
458 assert_eq!(clause.len(), 4);
459 }
460 }
461
462 #[test]
463 fn test_problem_sizes() {
464 let sat = Satisfiability::<i32>::new(3, vec![CNFClause::new(vec![1, 2, 3, 4])]);
465
466 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
467
468 let source_size = reduction.source_size();
469 let target_size = reduction.target_size();
470
471 assert_eq!(source_size.get("num_vars"), Some(3));
472 assert_eq!(target_size.get("k"), Some(3));
473 }
474
475 #[test]
476 fn test_empty_sat_to_3sat() {
477 let sat = Satisfiability::<i32>::new(3, vec![]);
478
479 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
480 let ksat = reduction.target_problem();
481
482 assert_eq!(ksat.num_clauses(), 0);
483 assert_eq!(ksat.num_vars(), 3);
484 }
485
486 #[test]
487 fn test_mixed_clause_sizes() {
488 let sat = Satisfiability::<i32>::new(
489 5,
490 vec![
491 CNFClause::new(vec![1]), CNFClause::new(vec![2, 3]), CNFClause::new(vec![1, 2, 3]), CNFClause::new(vec![1, 2, 3, 4]), CNFClause::new(vec![1, 2, 3, 4, 5]), ],
497 );
498
499 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
500 let ksat = reduction.target_problem();
501
502 for clause in ksat.clauses() {
504 assert_eq!(clause.len(), 3);
505 }
506
507 let solver = BruteForce::new();
509 let sat_solutions = solver.find_best(&sat);
510 let ksat_solutions = solver.find_best(ksat);
511
512 let sat_satisfiable = sat_solutions.iter().any(|s| sat.solution_size(s).is_valid);
513 let ksat_satisfiable = ksat_solutions.iter().any(|s| ksat.solution_size(s).is_valid);
514 assert_eq!(sat_satisfiable, ksat_satisfiable);
515 }
516
517 #[test]
518 fn test_unsatisfiable_formula() {
519 let sat = Satisfiability::<i32>::new(
521 1,
522 vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])],
523 );
524
525 let reduction = ReduceTo::<KSatisfiability<3, i32>>::reduce_to(&sat);
526 let ksat = reduction.target_problem();
527
528 let solver = BruteForce::new();
529
530 let sat_solutions = solver.find_best(&sat);
532 let ksat_solutions = solver.find_best(ksat);
533
534 let sat_satisfiable = sat_solutions.iter().any(|s| sat.solution_size(s).is_valid);
535 let ksat_satisfiable = ksat_solutions.iter().any(|s| ksat.solution_size(s).is_valid);
536
537 assert!(!sat_satisfiable);
538 assert!(!ksat_satisfiable);
539 }
540}
541
542use crate::poly;
544use crate::rules::registry::{ReductionEntry, ReductionOverhead};
545
546inventory::submit! {
547 ReductionEntry {
548 source_name: "Satisfiability",
549 target_name: "KSatisfiability",
550 source_graph: "CNF",
551 target_graph: "KCNF",
552 overhead_fn: || ReductionOverhead::new(vec![
553 ("num_clauses", poly!(num_clauses)),
554 ("num_vars", poly!(num_vars)),
555 ]),
556 }
557}
558
559inventory::submit! {
560 ReductionEntry {
561 source_name: "KSatisfiability",
562 target_name: "Satisfiability",
563 source_graph: "KCNF",
564 target_graph: "CNF",
565 overhead_fn: || ReductionOverhead::new(vec![
566 ("num_clauses", poly!(num_clauses)),
567 ("num_vars", poly!(num_vars)),
568 ]),
569 }
570}