Skip to main content

sat_interface/
lib.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This crate provides an abstraction layer to describe all SAT solvers. It provides a common
5//! interface for different implementations of SAT solvers.
6//!
7//! c.f. [SatSolver], [SolverState]
8
9#[cfg(feature = "cadical")]
10pub mod cadical;
11pub mod multisolver;
12
13use std::collections::HashSet;
14use std::fmt::{Display, Formatter};
15use std::vec::IntoIter;
16
17/// This trait provides an abstraction for a solver state.
18///
19/// Invariant: exactly one of [Self::is_sat], [Self::is_unsat], and [Self::is_unknown] should be true.
20pub trait SolverState {
21    fn is_sat(&self) -> bool;
22    fn is_unsat(&self) -> bool;
23    fn is_unknown(&self) -> bool;
24}
25
26/// This trait captures the ability to add constraints to a SAT solver.
27pub trait AddConstraints<C> {
28    fn insert(&mut self, constraints: &C);
29}
30
31impl<T, C> AddConstraints<C> for [T]
32where
33    T: AddConstraints<C>,
34{
35    fn insert(&mut self, constraints: &C) {
36        for t in self {
37            t.insert(constraints);
38        }
39    }
40}
41
42/// This trait captures the interface of a SAT solver.
43///
44/// Its trait constraints require that its implementation is able to add clauses and formulas as constraints.
45pub trait SatSolver: AddConstraints<Clause> + AddConstraints<Formula> {
46    type Status: SolverState;
47
48    /// Create a new SAT solver
49    fn new() -> Self;
50    /// Decide whether current constraints are satisfiable or not
51    fn solve(&mut self) -> Self::Status;
52    /// After [Self::solve] returns sat, query the solver for the boolean value of the SAT variable `lit`.
53    ///
54    /// Requirement: `lit` must be a SAT variable present in the constraints.
55    fn val(&mut self, lit: i32) -> bool;
56    /// After [Self::solve] returns sat, obtain the full model.
57    fn load_model(&mut self) -> Model;
58
59    /// After [Self::solve] returns sat, block the current model. It is useful for model enumeration.
60    fn block_model(&mut self);
61}
62
63/// A clause; it's a list of disjoined literals
64#[derive(Debug, Clone, Eq, PartialEq)]
65pub struct Clause(pub Vec<i32>);
66
67/// A formula; it's a list of conjoined clauses
68#[derive(Debug, Clone, Eq, PartialEq)]
69pub struct Formula(pub Vec<Clause>);
70
71/// A model as a vector mapping literals to boolea values
72///
73/// Note that the indices are off by one as SAT variables start at 1.
74#[derive(Debug, Clone, Eq, PartialEq)]
75pub struct Model(pub Vec<bool>);
76
77impl Display for Clause {
78    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
79        f.write_str("[")?;
80        for i in 0..self.0.len() {
81            write!(f, "{}", self.0[i])?;
82            if i < self.0.len() - 1 {
83                f.write_str(", ")?;
84            }
85        }
86        f.write_str("]")
87    }
88}
89
90impl Display for Formula {
91    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
92        f.write_str("[")?;
93        for i in 0..self.0.len() {
94            write!(f, "{}", self.0[i])?;
95            if i < self.0.len() - 1 {
96                f.write_str(";\n")?;
97            }
98        }
99        f.write_str("]")
100    }
101}
102
103impl Clause {
104    /// An empty clause
105    pub fn empty() -> Self {
106        Self::new(vec![])
107    }
108
109    /// A new clause with specified literals
110    pub fn new(lits: Vec<i32>) -> Self {
111        Clause(lits)
112    }
113
114    /// A singleton clause
115    pub fn single(lit: i32) -> Self {
116        Self::new(vec![lit])
117    }
118
119    /// Obtain a slice of the literals
120    pub fn lits(&self) -> &[i32] {
121        &self.0
122    }
123
124    fn filter_by_model<'a, 'b>(&'a self, model: &'b Model) -> impl Iterator<Item = &'a i32> + 'a
125    where
126        'b: 'a,
127    {
128        self.0.iter().filter(|&i| {
129            let idx = (i.abs() - 1) as usize;
130            if *i < 0 { !model.0[idx] } else { model.0[idx] }
131        })
132    }
133
134    /// Given a model, check whether the current clause is true.
135    pub fn evaluate(&self, model: &Model) -> bool {
136        self.filter_by_model(model).any(|_| true)
137    }
138
139    /// Return a list of true literals
140    pub fn find_true_vars(&self, model: &Model) -> Vec<i32> {
141        self.filter_by_model(model).copied().collect()
142    }
143
144    /// Return a list of true literals that are not in the given `set`
145    pub fn filter_true_vars(&self, model: &Model, set: &HashSet<i32>) -> Vec<i32> {
146        self.filter_by_model(model)
147            .filter(|&i| !set.contains(i))
148            .copied()
149            .collect()
150    }
151
152    /// Return a list of true literals as a clause
153    ///
154    /// c.f. [Self::find_true_vars]
155    pub fn filter_vars(&self, model: &Model) -> Clause {
156        Clause(self.find_true_vars(model))
157    }
158
159    /// Return an iterator for the literals
160    pub fn iter(&self) -> impl Iterator<Item = &i32> {
161        self.0.iter()
162    }
163
164    /// Concatenate two clauses
165    pub fn concat(&self, clause: &Clause) -> Self {
166        let mut c = self.clone();
167        c.concat_mut(clause);
168        c
169    }
170
171    /// Concatenate a given clause by modifying the self clause
172    pub fn concat_mut(&mut self, clause: &Clause) -> &mut Self {
173        self.0.extend(&clause.0);
174        self
175    }
176}
177
178impl IntoIterator for Clause {
179    type Item = i32;
180    type IntoIter = IntoIter<i32>;
181
182    fn into_iter(self) -> Self::IntoIter {
183        self.0.into_iter()
184    }
185}
186
187impl Formula {
188    /// An empty formula
189    pub fn empty() -> Formula {
190        Formula(vec![])
191    }
192
193    /// A new formula with the given vector of clauses
194    pub fn new(clauses: Vec<Clause>) -> Formula {
195        Formula(clauses)
196    }
197
198    /// Add a clause to the current formula
199    pub fn add(&mut self, clause: Clause) {
200        self.0.push(clause);
201    }
202
203    /// Return a formula only containing true literals in each clause
204    pub fn filter_clauses(&self, model: &Model) -> Formula {
205        Formula(self.0.iter().map(|c| c.filter_vars(model)).collect())
206    }
207
208    fn find_implicant_helper(self) -> HashSet<i32> {
209        let mut vset = HashSet::new();
210        let mut clauses: Vec<_> = self.0;
211
212        loop {
213            let begin_sz = vset.len();
214            let mut kept_clauses = vec![];
215            for clause in clauses.into_iter() {
216                // 1. if clause has a variable in vset, then this clause is true and we drop it.
217                // 2. if not, then does it have only one variable? if so, then we add the variable to vset.
218                // 3. otherwise, we keep clause.
219
220                if clause.0.iter().any(|&i| vset.contains(&i)) {
221                    continue;
222                }
223                if clause.0.len() == 1 {
224                    vset.insert(clause.0[0]);
225                } else {
226                    kept_clauses.push(clause);
227                }
228            }
229            clauses = kept_clauses;
230            if clauses.is_empty() {
231                break;
232            }
233            if begin_sz == vset.len() {
234                // we've known clauses is non-empty.
235                let c = clauses.pop().unwrap();
236                vset.insert(c.0[0]);
237            }
238        }
239
240        vset
241    }
242
243    /// Obtain an implicant of the formula based on the model
244    pub fn find_implicant(&self, model: &Model) -> HashSet<i32> {
245        self.filter_clauses(model).find_implicant_helper()
246    }
247
248    pub fn iter(&self) -> impl Iterator<Item = &Clause> {
249        self.0.iter()
250    }
251
252    pub fn concat(&self, clause: &Clause) -> Self {
253        let mut f = self.clone();
254        f.concat_mut(clause);
255        f
256    }
257
258    pub fn concat_mut(&mut self, clause: &Clause) -> &mut Self {
259        for c in self.0.iter_mut() {
260            c.concat_mut(clause);
261        }
262        self
263    }
264
265    pub fn distribute(&self, formula: &Formula) -> Self {
266        Formula::new(formula.iter().flat_map(|c| self.concat(c)).collect())
267    }
268
269    pub fn distribute_mut(&mut self, formula: &Formula) -> &mut Self {
270        self.0 = self.distribute(formula).0;
271        self
272    }
273}
274
275impl IntoIterator for Formula {
276    type Item = Clause;
277    type IntoIter = IntoIter<Clause>;
278
279    fn into_iter(self) -> Self::IntoIter {
280        self.0.into_iter()
281    }
282}
283
284impl Model {
285    /// Obtain a model based on a given evaluation function `f`
286    pub fn get_model<F>(vn: usize, f: F) -> Model
287    where
288        F: FnMut(usize) -> bool,
289    {
290        Model((1..vn + 1).map(f).collect())
291    }
292
293    /// Return a blocking clause for the model
294    pub fn negate(&self) -> Clause {
295        Clause(
296            self.0
297                .iter()
298                .enumerate()
299                .map(|(idx, v)| {
300                    if *v {
301                        -(idx as i32 + 1)
302                    } else {
303                        idx as i32 + 1
304                    }
305                })
306                .collect(),
307        )
308    }
309}