problemreductions/models/formula/
sat.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "Satisfiability",
15 display_name: "Satisfiability",
16 aliases: &["SAT"],
17 dimensions: &[],
18 module_path: module_path!(),
19 description: "Find satisfying assignment for CNF formula",
20 fields: &[
21 FieldInfo { name: "num_vars", type_name: "usize", description: "Number of Boolean variables" },
22 FieldInfo { name: "clauses", type_name: "Vec<CNFClause>", description: "Clauses in conjunctive normal form" },
23 ],
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub struct CNFClause {
38 pub literals: Vec<i32>,
40}
41
42impl CNFClause {
43 pub fn new(literals: Vec<i32>) -> Self {
48 Self { literals }
49 }
50
51 pub fn is_satisfied(&self, assignment: &[bool]) -> bool {
56 self.literals.iter().any(|&lit| {
57 let var = lit.unsigned_abs() as usize - 1; let value = assignment.get(var).copied().unwrap_or(false);
59 if lit > 0 {
60 value
61 } else {
62 !value
63 }
64 })
65 }
66
67 pub fn variables(&self) -> Vec<usize> {
69 self.literals
70 .iter()
71 .map(|&lit| lit.unsigned_abs() as usize - 1)
72 .collect()
73 }
74
75 pub fn len(&self) -> usize {
77 self.literals.len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.literals.is_empty()
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct Satisfiability {
118 num_vars: usize,
120 clauses: Vec<CNFClause>,
122}
123
124impl Satisfiability {
125 pub fn new(num_vars: usize, clauses: Vec<CNFClause>) -> Self {
127 Self { num_vars, clauses }
128 }
129
130 pub fn num_vars(&self) -> usize {
132 self.num_vars
133 }
134
135 pub fn num_clauses(&self) -> usize {
137 self.clauses.len()
138 }
139
140 pub fn num_literals(&self) -> usize {
142 self.clauses.iter().map(|c| c.len()).sum()
143 }
144
145 pub fn clauses(&self) -> &[CNFClause] {
147 &self.clauses
148 }
149
150 pub fn get_clause(&self, index: usize) -> Option<&CNFClause> {
152 self.clauses.get(index)
153 }
154
155 pub fn count_satisfied(&self, assignment: &[bool]) -> usize {
157 self.clauses
158 .iter()
159 .filter(|c| c.is_satisfied(assignment))
160 .count()
161 }
162
163 pub fn is_satisfying(&self, assignment: &[bool]) -> bool {
165 self.clauses.iter().all(|c| c.is_satisfied(assignment))
166 }
167
168 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
172 self.evaluate(config).0
173 }
174}
175
176impl Problem for Satisfiability {
177 const NAME: &'static str = "Satisfiability";
178 type Value = crate::types::Or;
179
180 fn dims(&self) -> Vec<usize> {
181 vec![2; self.num_vars]
182 }
183
184 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
185 crate::types::Or({
186 let assignment = super::config_to_assignment(config);
187 self.is_satisfying(&assignment)
188 })
189 }
190
191 fn variant() -> Vec<(&'static str, &'static str)> {
192 crate::variant_params![]
193 }
194}
195
196crate::declare_variants! {
197 default Satisfiability => "2^num_variables",
198}
199
200#[cfg(test)]
207pub(crate) fn is_satisfying_assignment(
208 _num_vars: usize,
209 clauses: &[Vec<i32>],
210 assignment: &[bool],
211) -> bool {
212 clauses.iter().all(|clause| {
213 clause.iter().any(|&lit| {
214 let var = lit.unsigned_abs() as usize - 1;
215 let value = assignment.get(var).copied().unwrap_or(false);
216 if lit > 0 {
217 value
218 } else {
219 !value
220 }
221 })
222 })
223}
224
225#[cfg(feature = "example-db")]
226pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
227 vec![crate::example_db::specs::ModelExampleSpec {
228 id: "satisfiability",
229 instance: Box::new(Satisfiability::new(
230 3,
231 vec![
232 CNFClause::new(vec![1, 2]),
233 CNFClause::new(vec![-1, 3]),
234 CNFClause::new(vec![-2, -3]),
235 ],
236 )),
237 optimal_config: vec![0, 1, 0],
238 optimal_value: serde_json::json!(true),
239 }]
240}
241
242#[cfg(test)]
243#[path = "../../unit_tests/models/formula/sat.rs"]
244mod tests;