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;