1use crate::models::graph::DominatingSet;
18use crate::models::satisfiability::Satisfiability;
19use crate::rules::sat_independentset::BoolVar;
20use crate::rules::traits::{ReduceTo, ReductionResult};
21use crate::traits::Problem;
22use crate::types::ProblemSize;
23use num_traits::{Num, Zero};
24use std::ops::AddAssign;
25
26#[derive(Debug, Clone)]
33pub struct ReductionSATToDS<W> {
34 target: DominatingSet<W>,
36 num_literals: usize,
38 num_clauses: usize,
40 source_size: ProblemSize,
42}
43
44impl<W> ReductionResult for ReductionSATToDS<W>
45where
46 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + 'static,
47{
48 type Source = Satisfiability<W>;
49 type Target = DominatingSet<W>;
50
51 fn target_problem(&self) -> &Self::Target {
52 &self.target
53 }
54
55 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
66 let selected_count: usize = target_solution.iter().sum();
67
68 if selected_count > self.num_literals {
71 return vec![0; self.num_literals];
73 }
74
75 let mut assignment = vec![0usize; self.num_literals];
76
77 for (i, &value) in target_solution.iter().enumerate() {
78 if value == 1 {
79 if i >= 3 * self.num_literals {
81 continue; }
83
84 let var_index = i / 3;
85 let vertex_type = i % 3;
86
87 match vertex_type {
88 0 => {
89 assignment[var_index] = 1;
91 }
92 1 => {
93 assignment[var_index] = 0;
95 }
96 2 => {
97 }
100 _ => unreachable!(),
101 }
102 }
103 }
104
105 assignment
106 }
107
108 fn source_size(&self) -> ProblemSize {
109 self.source_size.clone()
110 }
111
112 fn target_size(&self) -> ProblemSize {
113 self.target.problem_size()
114 }
115}
116
117impl<W> ReductionSATToDS<W> {
118 pub fn num_literals(&self) -> usize {
120 self.num_literals
121 }
122
123 pub fn num_clauses(&self) -> usize {
125 self.num_clauses
126 }
127}
128
129impl<W> ReduceTo<DominatingSet<W>> for Satisfiability<W>
130where
131 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
132{
133 type Result = ReductionSATToDS<W>;
134
135 fn reduce_to(&self) -> Self::Result {
136 let num_variables = self.num_vars();
137 let num_clauses = self.num_clauses();
138
139 let num_vertices = 3 * num_variables + num_clauses;
141
142 let mut edges: Vec<(usize, usize)> = Vec::new();
143
144 for i in 0..num_variables {
151 let base = 3 * i;
152 edges.push((base, base + 1));
154 edges.push((base, base + 2));
155 edges.push((base + 1, base + 2));
156 }
157
158 for (j, clause) in self.clauses().iter().enumerate() {
161 let clause_vertex = 3 * num_variables + j;
162
163 for &lit in &clause.literals {
164 let var = BoolVar::from_literal(lit);
165 let literal_vertex = if var.neg {
169 3 * var.name + 1
170 } else {
171 3 * var.name
172 };
173 edges.push((literal_vertex, clause_vertex));
174 }
175 }
176
177 let target = DominatingSet::new(num_vertices, edges);
178
179 ReductionSATToDS {
180 target,
181 num_literals: num_variables,
182 num_clauses,
183 source_size: self.problem_size(),
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::models::satisfiability::CNFClause;
192 use crate::solvers::{BruteForce, Solver};
193
194 #[test]
195 fn test_simple_sat_to_ds() {
196 let sat = Satisfiability::<i32>::new(1, vec![CNFClause::new(vec![1])]);
198 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
199 let ds_problem = reduction.target_problem();
200
201 assert_eq!(ds_problem.num_vertices(), 4);
203
204 assert_eq!(ds_problem.num_edges(), 4);
208 }
209
210 #[test]
211 fn test_two_variable_sat_to_ds() {
212 let sat = Satisfiability::<i32>::new(2, vec![CNFClause::new(vec![1, 2])]);
214 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
215 let ds_problem = reduction.target_problem();
216
217 assert_eq!(ds_problem.num_vertices(), 7);
219
220 assert_eq!(ds_problem.num_edges(), 8);
225 }
226
227 #[test]
228 fn test_satisfiable_formula() {
229 let sat = Satisfiability::<i32>::new(
232 2,
233 vec![
234 CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 2]), ],
237 );
238 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
239 let ds_problem = reduction.target_problem();
240
241 let solver = BruteForce::new();
243 let solutions = solver.find_best(ds_problem);
244
245 let min_size = solutions[0].iter().sum::<usize>();
247 assert_eq!(min_size, 2, "Minimum dominating set should have 2 vertices");
248
249 let mut found_satisfying = false;
251 for sol in &solutions {
252 let sat_sol = reduction.extract_solution(sol);
253 let assignment: Vec<bool> = sat_sol.iter().map(|&v| v == 1).collect();
254 if sat.is_satisfying(&assignment) {
255 found_satisfying = true;
256 break;
257 }
258 }
259 assert!(found_satisfying, "Should find a satisfying assignment");
260 }
261
262 #[test]
263 fn test_unsatisfiable_formula() {
264 let sat = Satisfiability::<i32>::new(
266 1,
267 vec![CNFClause::new(vec![1]), CNFClause::new(vec![-1])],
268 );
269 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
270 let ds_problem = reduction.target_problem();
271
272 assert_eq!(ds_problem.num_vertices(), 5);
274
275 let solver = BruteForce::new();
276 let solutions = solver.find_best(ds_problem);
277
278 for sol in &solutions {
292 let sat_sol = reduction.extract_solution(sol);
293 let assignment: Vec<bool> = sat_sol.iter().map(|&v| v == 1).collect();
294 assert!(
296 !sat.is_satisfying(&assignment),
297 "Unsatisfiable formula should not be satisfied"
298 );
299 }
300 }
301
302 #[test]
303 fn test_three_sat_example() {
304 let sat = Satisfiability::<i32>::new(
306 3,
307 vec![
308 CNFClause::new(vec![1, 2, 3]), CNFClause::new(vec![-1, -2, 3]), CNFClause::new(vec![1, -2, -3]), ],
312 );
313
314 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
315 let ds_problem = reduction.target_problem();
316
317 assert_eq!(ds_problem.num_vertices(), 12);
319
320 let solver = BruteForce::new();
321 let solutions = solver.find_best(ds_problem);
322
323 let min_size = solutions[0].iter().sum::<usize>();
325 assert_eq!(min_size, 3, "Minimum dominating set should have 3 vertices");
326
327 let mut found_satisfying = false;
329 for sol in &solutions {
330 let sat_sol = reduction.extract_solution(sol);
331 let assignment: Vec<bool> = sat_sol.iter().map(|&v| v == 1).collect();
332 if sat.is_satisfying(&assignment) {
333 found_satisfying = true;
334 break;
335 }
336 }
337 assert!(found_satisfying, "Should find a satisfying assignment for 3-SAT");
338 }
339
340 #[test]
341 fn test_extract_solution_positive_literal() {
342 let sat = Satisfiability::<i32>::new(1, vec![CNFClause::new(vec![1])]);
344 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
345
346 let ds_sol = vec![1, 0, 0, 0];
349 let sat_sol = reduction.extract_solution(&ds_sol);
350 assert_eq!(sat_sol, vec![1]); }
352
353 #[test]
354 fn test_extract_solution_negative_literal() {
355 let sat = Satisfiability::<i32>::new(1, vec![CNFClause::new(vec![-1])]);
357 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
358
359 let ds_sol = vec![0, 1, 0, 0];
362 let sat_sol = reduction.extract_solution(&ds_sol);
363 assert_eq!(sat_sol, vec![0]); }
365
366 #[test]
367 fn test_extract_solution_dummy() {
368 let sat = Satisfiability::<i32>::new(2, vec![CNFClause::new(vec![1])]);
370 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
371
372 let ds_sol = vec![1, 0, 0, 0, 0, 1, 0];
376 let sat_sol = reduction.extract_solution(&ds_sol);
377 assert_eq!(sat_sol, vec![1, 0]); }
379
380 #[test]
381 fn test_source_and_target_size() {
382 let sat = Satisfiability::<i32>::new(
383 3,
384 vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 3])],
385 );
386 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
387
388 let source_size = reduction.source_size();
389 let target_size = reduction.target_size();
390
391 assert_eq!(source_size.get("num_vars"), Some(3));
392 assert_eq!(source_size.get("num_clauses"), Some(2));
393 assert_eq!(target_size.get("num_vertices"), Some(11));
395 }
396
397 #[test]
398 fn test_empty_sat() {
399 let sat = Satisfiability::<i32>::new(0, vec![]);
401 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
402 let ds_problem = reduction.target_problem();
403
404 assert_eq!(ds_problem.num_vertices(), 0);
405 assert_eq!(ds_problem.num_edges(), 0);
406 assert_eq!(reduction.num_clauses(), 0);
407 assert_eq!(reduction.num_literals(), 0);
408 }
409
410 #[test]
411 fn test_multiple_literals_same_variable() {
412 let sat = Satisfiability::<i32>::new(1, vec![CNFClause::new(vec![1, -1])]);
414 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
415 let ds_problem = reduction.target_problem();
416
417 assert_eq!(ds_problem.num_vertices(), 4);
419
420 assert_eq!(ds_problem.num_edges(), 5);
424 }
425
426 #[test]
427 fn test_sat_ds_solution_correspondence() {
428 let sat = Satisfiability::<i32>::new(
430 2,
431 vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, -2])],
432 );
433
434 let sat_solver = BruteForce::new();
436 let direct_sat_solutions = sat_solver.find_best(&sat);
437
438 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
440 let ds_problem = reduction.target_problem();
441 let ds_solutions = sat_solver.find_best(ds_problem);
442
443 for sol in &direct_sat_solutions {
445 let assignment: Vec<bool> = sol.iter().map(|&v| v == 1).collect();
446 assert!(sat.is_satisfying(&assignment));
447 }
448
449 let min_size = ds_solutions[0].iter().sum::<usize>();
451 if min_size == 2 {
452 let mut found_satisfying = false;
454 for sol in &ds_solutions {
455 if sol.iter().sum::<usize>() == 2 {
456 let sat_sol = reduction.extract_solution(sol);
457 let assignment: Vec<bool> = sat_sol.iter().map(|&v| v == 1).collect();
458 if sat.is_satisfying(&assignment) {
459 found_satisfying = true;
460 break;
461 }
462 }
463 }
464 assert!(found_satisfying, "At least one DS solution should give a SAT solution");
465 }
466 }
467
468 #[test]
469 fn test_accessors() {
470 let sat = Satisfiability::<i32>::new(
471 2,
472 vec![CNFClause::new(vec![1, -2])],
473 );
474 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
475
476 assert_eq!(reduction.num_literals(), 2);
477 assert_eq!(reduction.num_clauses(), 1);
478 }
479
480 #[test]
481 fn test_extract_solution_too_many_selected() {
482 let sat = Satisfiability::<i32>::new(1, vec![CNFClause::new(vec![1])]);
484 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
485
486 let ds_sol = vec![1, 1, 1, 1];
488 let sat_sol = reduction.extract_solution(&ds_sol);
489 assert_eq!(sat_sol, vec![0]);
491 }
492
493 #[test]
494 fn test_negated_variable_connection() {
495 let sat = Satisfiability::<i32>::new(2, vec![CNFClause::new(vec![-1, -2])]);
497 let reduction = ReduceTo::<DominatingSet<i32>>::reduce_to(&sat);
498 let ds_problem = reduction.target_problem();
499
500 assert_eq!(ds_problem.num_vertices(), 7);
502
503 assert_eq!(ds_problem.num_edges(), 8);
508 }
509}
510
511use crate::poly;
513use crate::rules::registry::{ReductionEntry, ReductionOverhead};
514
515inventory::submit! {
516 ReductionEntry {
517 source_name: "Satisfiability",
518 target_name: "DominatingSet",
519 source_graph: "CNF",
520 target_graph: "SimpleGraph",
521 overhead_fn: || ReductionOverhead::new(vec![
522 ("num_vertices", poly!(num_vars)),
523 ("num_edges", poly!(num_clauses)),
524 ]),
525 }
526}