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, Hash)]
65pub struct Clause(pub Vec<i32>);
66
67/// A formula; it's a list of conjoined clauses
68#[derive(Debug, Clone, Eq, PartialEq, Hash)]
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, Hash)]
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    /// Add a new literal to the clause
178    pub fn add(&mut self, lit: i32) -> &mut Self {
179        self.0.push(lit);
180        self
181    }
182}
183
184impl IntoIterator for Clause {
185    type Item = i32;
186    type IntoIter = IntoIter<i32>;
187
188    fn into_iter(self) -> Self::IntoIter {
189        self.0.into_iter()
190    }
191}
192
193impl Formula {
194    /// An empty formula
195    pub fn empty() -> Formula {
196        Formula(vec![])
197    }
198
199    /// A new formula with the given vector of clauses
200    pub fn new(clauses: Vec<Clause>) -> Formula {
201        Formula(clauses)
202    }
203
204    /// Add a clause to the current formula
205    pub fn add(&mut self, clause: Clause) {
206        self.0.push(clause);
207    }
208
209    /// Return a formula only containing true literals in each clause
210    pub fn filter_clauses(&self, model: &Model) -> Formula {
211        Formula(self.0.iter().map(|c| c.filter_vars(model)).collect())
212    }
213
214    fn find_implicant_helper(self) -> HashSet<i32> {
215        let mut vset = HashSet::new();
216        let mut clauses: Vec<_> = self.0;
217
218        loop {
219            let begin_sz = vset.len();
220            let mut kept_clauses = vec![];
221            for clause in clauses.into_iter() {
222                // 1. if clause has a variable in vset, then this clause is true and we drop it.
223                // 2. if not, then does it have only one variable? if so, then we add the variable to vset.
224                // 3. otherwise, we keep clause.
225
226                if clause.0.iter().any(|&i| vset.contains(&i)) {
227                    continue;
228                }
229                if clause.0.len() == 1 {
230                    vset.insert(clause.0[0]);
231                } else {
232                    kept_clauses.push(clause);
233                }
234            }
235            clauses = kept_clauses;
236            if clauses.is_empty() {
237                break;
238            }
239            if begin_sz == vset.len() {
240                // we've known clauses is non-empty.
241                let c = clauses.pop().unwrap();
242                vset.insert(c.0[0]);
243            }
244        }
245
246        vset
247    }
248
249    /// Obtain an implicant of the formula based on the model
250    pub fn find_implicant(&self, model: &Model) -> HashSet<i32> {
251        self.filter_clauses(model).find_implicant_helper()
252    }
253
254    pub fn iter(&self) -> impl Iterator<Item = &Clause> {
255        self.0.iter()
256    }
257
258    pub fn concat(&self, clause: &Clause) -> Self {
259        let mut f = self.clone();
260        f.concat_mut(clause);
261        f
262    }
263
264    pub fn concat_mut(&mut self, clause: &Clause) -> &mut Self {
265        for c in self.0.iter_mut() {
266            c.concat_mut(clause);
267        }
268        self
269    }
270
271    pub fn distribute(&self, formula: &Formula) -> Self {
272        Formula::new(formula.iter().flat_map(|c| self.concat(c)).collect())
273    }
274
275    pub fn distribute_mut(&mut self, formula: &Formula) -> &mut Self {
276        self.0 = self.distribute(formula).0;
277        self
278    }
279}
280
281impl IntoIterator for Formula {
282    type Item = Clause;
283    type IntoIter = IntoIter<Clause>;
284
285    fn into_iter(self) -> Self::IntoIter {
286        self.0.into_iter()
287    }
288}
289
290impl Model {
291    /// Obtain a model based on a given evaluation function `f`
292    pub fn get_model<F>(vn: usize, f: F) -> Model
293    where
294        F: FnMut(usize) -> bool,
295    {
296        Model((1..vn + 1).map(f).collect())
297    }
298
299    /// Return a blocking clause for the model
300    pub fn negate(&self) -> Clause {
301        Clause(
302            self.0
303                .iter()
304                .enumerate()
305                .map(|(idx, v)| {
306                    if *v {
307                        -(idx as i32 + 1)
308                    } else {
309                        idx as i32 + 1
310                    }
311                })
312                .collect(),
313        )
314    }
315}