Skip to main content

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(feature = "example-db")]
179pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
180    use crate::export::SolutionPair;
181    use crate::models::formula::CNFClause;
182
183    vec![crate::example_db::specs::RuleExampleSpec {
184        id: "satisfiability_to_minimumdominatingset",
185        build: || {
186            let source = Satisfiability::new(
187                5,
188                vec![
189                    CNFClause::new(vec![1, 2, -3]),
190                    CNFClause::new(vec![-1, 3, 4]),
191                    CNFClause::new(vec![2, -4, 5]),
192                    CNFClause::new(vec![-2, 3, -5]),
193                    CNFClause::new(vec![1, -3, 5]),
194                    CNFClause::new(vec![-1, -2, 4]),
195                    CNFClause::new(vec![3, -4, -5]),
196                ],
197            );
198            crate::example_db::specs::rule_example_with_witness::<
199                _,
200                MinimumDominatingSet<SimpleGraph, i32>,
201            >(
202                source,
203                SolutionPair {
204                    source_config: vec![1, 0, 1, 1, 1],
205                    target_config: vec![
206                        1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207                    ],
208                },
209            )
210        },
211    }]
212}
213
214#[cfg(test)]
215#[path = "../unit_tests/rules/sat_minimumdominatingset.rs"]
216mod tests;