problemreductions/rules/sat_minimumdominatingset.rs
1//! Reduction from Satisfiability (SAT) to MinimumDominatingSet.
2//!
3//! The reduction follows this construction:
4//! 1. For each variable x_i, create a "variable gadget" with 3 vertices:
5//! - Vertex for positive literal x_i
6//! - Vertex for negative literal NOT x_i
7//! - A dummy vertex
8//! These 3 vertices form a complete triangle (clique).
9//! 2. For each clause C_j, create a clause vertex.
10//! 3. Connect each clause vertex to the literal vertices that appear in that clause.
11//!
12//! A dominating set of size = num_variables corresponds to a satisfying assignment:
13//! - Selecting the positive literal vertex means the variable is true
14//! - Selecting the negative literal vertex means the variable is false
15//! - Selecting the dummy vertex means the variable can be either (unused in any clause)
16
17use crate::models::formula::Satisfiability;
18use crate::models::graph::MinimumDominatingSet;
19use crate::reduction;
20use crate::rules::sat_maximumindependentset::BoolVar;
21use crate::rules::traits::{ReduceTo, ReductionResult};
22use crate::topology::SimpleGraph;
23
24/// Result of reducing Satisfiability to MinimumDominatingSet.
25///
26/// This struct contains:
27/// - The target MinimumDominatingSet problem
28/// - The number of literals (variables) in the source SAT problem
29/// - The number of clauses in the source SAT problem
30#[derive(Debug, Clone)]
31pub struct ReductionSATToDS {
32 /// The target MinimumDominatingSet problem.
33 target: MinimumDominatingSet<SimpleGraph, i32>,
34 /// The number of variables in the source SAT problem.
35 num_literals: usize,
36 /// The number of clauses in the source SAT problem.
37 num_clauses: usize,
38}
39
40impl ReductionResult for ReductionSATToDS {
41 type Source = Satisfiability;
42 type Target = MinimumDominatingSet<SimpleGraph, i32>;
43
44 fn target_problem(&self) -> &Self::Target {
45 &self.target
46 }
47
48 /// Extract a SAT solution from a MinimumDominatingSet solution.
49 ///
50 /// The dominating set solution encodes variable assignments:
51 /// - For each variable x_i (0-indexed), vertices are at positions 3*i, 3*i+1, 3*i+2
52 /// - 3*i: positive literal x_i (selecting means x_i = true)
53 /// - 3*i+1: negative literal NOT x_i (selecting means x_i = false)
54 /// - 3*i+2: dummy vertex (selecting means x_i can be either)
55 ///
56 /// If more than num_literals vertices are selected, the solution is invalid
57 /// and we return a default assignment.
58 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
59 let selected_count: usize = target_solution.iter().sum();
60
61 // If more vertices selected than variables, not a minimal dominating set
62 // corresponding to a satisfying assignment
63 if selected_count > self.num_literals {
64 // Return default assignment (all false)
65 return vec![0; self.num_literals];
66 }
67
68 let mut assignment = vec![0usize; self.num_literals];
69
70 for (i, &value) in target_solution.iter().enumerate() {
71 if value == 1 {
72 // Only consider variable gadget vertices (first 3*num_literals vertices)
73 if i >= 3 * self.num_literals {
74 continue; // Skip clause vertices
75 }
76
77 let var_index = i / 3;
78 let vertex_type = i % 3;
79
80 match vertex_type {
81 0 => {
82 // Positive literal selected: x_i = true
83 assignment[var_index] = 1;
84 }
85 1 => {
86 // Negative literal selected: x_i = false
87 assignment[var_index] = 0;
88 }
89 2 => {
90 // Dummy vertex selected: variable is unconstrained
91 // Default to false (already 0), but could be anything
92 }
93 _ => unreachable!(),
94 }
95 }
96 }
97
98 assignment
99 }
100}
101
102impl ReductionSATToDS {
103 /// Get the number of literals (variables) in the source SAT problem.
104 pub fn num_literals(&self) -> usize {
105 self.num_literals
106 }
107
108 /// Get the number of clauses in the source SAT problem.
109 pub fn num_clauses(&self) -> usize {
110 self.num_clauses
111 }
112}
113
114#[reduction(
115 overhead = {
116 num_vertices = "3 * num_vars + num_clauses",
117 num_edges = "3 * num_vars + num_literals",
118 }
119)]
120impl ReduceTo<MinimumDominatingSet<SimpleGraph, i32>> for Satisfiability {
121 type Result = ReductionSATToDS;
122
123 fn reduce_to(&self) -> Self::Result {
124 let num_variables = self.num_vars();
125 let num_clauses = self.num_clauses();
126
127 // Total vertices: 3 per variable (for variable gadget) + 1 per clause
128 let num_vertices = 3 * num_variables + num_clauses;
129
130 let mut edges: Vec<(usize, usize)> = Vec::new();
131
132 // Step 1: Create variable gadgets
133 // For each variable i (0-indexed), vertices are at positions:
134 // 3*i: positive literal x_i
135 // 3*i+1: negative literal NOT x_i
136 // 3*i+2: dummy vertex
137 // These form a complete triangle (clique of 3)
138 for i in 0..num_variables {
139 let base = 3 * i;
140 // Add all edges of the triangle
141 edges.push((base, base + 1));
142 edges.push((base, base + 2));
143 edges.push((base + 1, base + 2));
144 }
145
146 // Step 2: Connect clause vertices to literal vertices
147 // Clause j gets vertex at position 3*num_variables + j
148 for (j, clause) in self.clauses().iter().enumerate() {
149 let clause_vertex = 3 * num_variables + j;
150
151 for &lit in &clause.literals {
152 let var = BoolVar::from_literal(lit);
153 // var.name is 0-indexed
154 // If positive literal, connect to vertex 3*var.name
155 // If negative literal, connect to vertex 3*var.name + 1
156 let literal_vertex = if var.neg {
157 3 * var.name + 1
158 } else {
159 3 * var.name
160 };
161 edges.push((literal_vertex, clause_vertex));
162 }
163 }
164
165 let target = MinimumDominatingSet::new(
166 SimpleGraph::new(num_vertices, edges),
167 vec![1i32; num_vertices],
168 );
169
170 ReductionSATToDS {
171 target,
172 num_literals: num_variables,
173 num_clauses,
174 }
175 }
176}
177
178#[cfg(test)]
179#[path = "../unit_tests/rules/sat_minimumdominatingset.rs"]
180mod tests;