sat_solver/sat/
solver.rs

1#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
2//! Defines common types and traits for SAT solvers, including solver configurations,
3//! solution representation, and statistics.
4//!
5//! This module provides:
6//! - `SolutionStats`: A struct to hold statistics about a solver's run (conflicts, decisions, etc.).
7//! - `Solutions`: A struct to represent a satisfying assignment (a model) for a formula.
8//! - `SolverConfig`: A trait to define a configuration for a SAT solver, specifying the
9//!   types for various components like literals, assignment management, variable selection,
10//!   propagation, restart strategy, and clause management.
11//! - `solver_config!`: A macro to easily create concrete implementations of `SolverConfig`.
12//! - `DefaultConfig`: A pre-defined default solver configuration using common component choices.
13//! - `Solver`: A trait defining the general interface for a SAT solver.
14
15use crate::sat::assignment::{AssignmentImpls, VecAssignment};
16use crate::sat::clause_management::{ClauseManagementImpls, LbdClauseManagement};
17use crate::sat::cnf::Cnf;
18use crate::sat::literal::{Literal, LiteralImpls, PackedLiteral};
19use crate::sat::propagation::{PropagatorImpls, WatchedLiterals};
20use crate::sat::restarter::{Luby, RestarterImpls};
21use crate::sat::variable_selection::{VariableSelectionImpls, Vsids};
22use clap::ValueEnum;
23use itertools::Itertools;
24use rustc_hash::FxHashSet;
25use smallvec::SmallVec;
26use std::fmt::{Debug, Display, Formatter};
27use std::num::NonZeroI32;
28
29/// Contains statistics about a SAT solver's execution.
30///
31/// These statistics provide insights into the solver's performance and behavior
32/// during the search for a solution.
33#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct SolutionStats {
35    /// The total number of conflicts encountered during the search.
36    pub conflicts: usize,
37    /// The total number of decisions made (variables chosen and assigned a value heuristically).
38    pub decisions: usize,
39    /// The total number of propagations (literals assigned due to unit propagation).
40    pub propagations: usize,
41    /// The total number of times the solver restarted its search.
42    pub restarts: usize,
43    /// The total number of clauses learnt during conflict analysis.
44    pub learnt_clauses: usize,
45    /// The total number of learnt clauses removed by clause database cleaning.
46    pub removed_clauses: usize,
47}
48
49/// Represents a satisfying assignment (a model) for a CNF formula.
50///
51/// The assignments are stored as a set of `NonZeroI32`, where:
52/// - A positive integer `v` means variable `v` is assigned true.
53/// - A negative integer `-v` means variable `v` is assigned false.
54///   This aligns with the DIMACS convention for representing literals.
55#[derive(Debug, Clone, PartialEq, Eq, Default)]
56pub struct Solutions {
57    /// A set of non-zero integers representing the truth assignments of variables.
58    /// Positive `v` means variable `v` is true; negative `-v` means variable `v` is false.
59    pub assignments: FxHashSet<NonZeroI32>,
60}
61
62impl Display for Solutions {
63    /// Formats the solution as a space-separated string of assigned literals.
64    /// For example: "1 -2 3" means x1=true, x2=false, x3=true.
65    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66        let assignments_str: Vec<String> = self
67            .assignments
68            .iter()
69            .sorted_by(|left, right| Ord::cmp(&left.abs(), &right.abs()))
70            .map(|&val| val.get().to_string())
71            .collect();
72
73        write!(f, "{}", assignments_str.join(" "))
74    }
75}
76
77impl Solutions {
78    /// Creates a new `Solutions` instance from a slice of `i32` (DIMACS literals).
79    /// Zero values in the input slice are ignored, as `NonZeroI32` cannot be zero.
80    #[must_use]
81    pub fn new(s: &[i32]) -> Self {
82        Self {
83            assignments: s.iter().copied().filter_map(NonZeroI32::new).collect(),
84        }
85    }
86
87    /// Returns an iterator over the assigned literals (`&NonZeroI32`).
88    pub fn iter(&self) -> impl Iterator<Item = &NonZeroI32> {
89        self.assignments.iter()
90    }
91
92    /// Checks if a given literal (represented by `NonZeroI32`) is part of the solution.
93    /// For example, `check(NonZeroI32::new(1).unwrap())` checks if variable 1 is true.
94    /// `check(NonZeroI32::new(-2).unwrap())` checks if variable 2 is false.
95    #[must_use]
96    pub fn check(&self, i: NonZeroI32) -> bool {
97        self.assignments.contains(&i)
98    }
99
100    /// Returns the number of variables assigned in this solution.
101    #[must_use]
102    pub fn len(&self) -> usize {
103        self.assignments.len()
104    }
105
106    /// Returns `true` if this solution assigns no variables.
107    #[must_use]
108    pub fn is_empty(&self) -> bool {
109        self.assignments.is_empty()
110    }
111
112    /// Adds a literal assignment to the solution set.
113    pub fn add(&mut self, i: NonZeroI32) {
114        self.assignments.insert(i);
115    }
116}
117
118/// A trait that defines the configuration for a SAT solver.
119///
120/// This trait uses associated types to specify the concrete types for various
121/// components of a SAT solver. This allows for a highly generic `Solver` trait
122/// that can be instantiated with different underlying implementations for its parts.
123pub trait SolverConfig: Debug + Clone {
124    /// The type used for managing variable assignments (e.g. `VecAssignment`).
125    /// Must implement `crate::sat::assignment::Assignment`.
126    type Assignment: crate::sat::assignment::Assignment;
127    /// The type used for the variable selection heuristic (e.g. `Vsids`, `Random`).
128    /// Must implement `crate::sat::variable_selection::VariableSelection`.
129    type VariableSelector: crate::sat::variable_selection::VariableSelection<Self::Literal>;
130    /// The type used to represent literals (e.g. `PackedLiteral`, `StructLiteral`).
131    /// Must implement `crate::sat::literal::Literal`.
132    type Literal: Literal;
133    /// The type used for storing literals within a clause (e.g. `Vec<L>`, `SmallVec<[L; N]>`).
134    /// Must implement `crate::sat::clause_storage::LiteralStorage`.
135    type LiteralStorage: LiteralStorage<Self::Literal>;
136    /// The type used for the restart strategy (e.g. `Luby`, `Geometric`).
137    /// Must implement `crate::sat::restarter::Restarter`.
138    type Restarter: crate::sat::restarter::Restarter;
139    /// The type used for the unit propagation mechanism (e.g. `WatchedLiterals`, `UnitSearch`).
140    /// Must implement `crate::sat::propagation::Propagator`.
141    type Propagator: crate::sat::propagation::Propagator<Self::Literal, Self::LiteralStorage, Self::Assignment>;
142    /// The type used for managing the clause database (e.g. `LbdClauseManagement`).
143    /// Must implement `crate::sat::clause_management::ClauseManagement`.
144    type ClauseManager: crate::sat::clause_management::ClauseManagement;
145}
146
147/// A macro to conveniently define a struct that implements `SolverConfig`.
148///
149/// This reduces boilerplate when creating new solver configurations.
150///
151/// # Usage
152///
153/// ## Without generic parameters:
154/// ```rust
155/// # use crate::sat::solver_types::{solver_config, SolverConfig};
156/// # use crate::sat::literal::PackedLiteral;
157/// # use smallvec::SmallVec;
158/// # use crate::sat::assignment::VecAssignment;
159/// # use crate::sat::variable_selection::Vsids;
160/// # use crate::sat::propagation::WatchedLiterals;
161/// # use crate::sat::restarter::Fixed;
162/// # use crate::sat::clause_management::NoClauseManagement;
163/// # #[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct MyLitStore;
164/// # impl crate::sat::clause_storage::LiteralStorage<PackedLiteral> for MyLitStore {
165/// #   fn push(&mut self, l: PackedLiteral) {} fn len(&self) -> usize {0} fn is_empty(&self) -> bool {true}
166/// #   fn iter(&self) -> std::slice::Iter<PackedLiteral> { [].iter() }
167/// #   fn iter_mut(&mut self) -> std::slice::IterMut<PackedLiteral> { [].iter_mut() }
168/// #   fn remove(&mut self, i: usize) -> PackedLiteral { PackedLiteral::default() } fn clear(&mut self) {}
169/// #   fn swap(&mut self, a: usize, b: usize) {}
170/// #   unsafe fn get_unchecked(&self, i: usize) -> &PackedLiteral { &PackedLiteral::default() }
171/// #   unsafe fn get_unchecked_mut(&mut self, i: usize) -> &mut PackedLiteral { panic!() }
172/// # }
173/// # impl FromIterator<PackedLiteral> for MyLitStore { fn from_iter<T: IntoIterator<Item=PackedLiteral>>(iter: T) -> Self { MyLitStore } }
174/// # impl From<Vec<PackedLiteral>> for MyLitStore { fn from(v: Vec<PackedLiteral>) -> Self { MyLitStore } }
175/// # impl std::ops::Index<usize> for MyLitStore { type Output = PackedLiteral; fn index(&self, i: usize) -> &Self::Output { &PackedLiteral::default() }}
176/// # impl std::ops::IndexMut<usize> for MyLitStore { fn index_mut(&mut self, i: usize) -> &mut Self::Output { panic!() }}
177/// # impl Extend<PackedLiteral> for MyLitStore { fn extend<T: IntoIterator<Item=PackedLiteral>>(&mut self, iter: T) {} }
178/// # impl AsRef<[PackedLiteral]> for MyLitStore { fn as_ref(&self) -> &[PackedLiteral] { &[] } }
179///
180/// solver_config!(MyCoolConfig {
181///     Literal = PackedLiteral,
182///     LiteralStorage = MyLitStore, // Using a placeholder for brevity
183///     Assignment = VecAssignment,
184///     VariableSelector = Vsids,
185///     Propagator = WatchedLiterals<PackedLiteral, MyLitStore, VecAssignment>,
186///     Restarter = Fixed<100>,
187///     ClauseManager = NoClauseManagement<PackedLiteral, MyLitStore>,
188/// });
189/// ```
190///
191/// ## With generic parameters:
192/// ```rust
193/// # use crate::sat::solver_types::{solver_config, SolverConfig};
194/// # use crate::sat::literal::{Literal, PackedLiteral};
195/// # use crate::sat::clause_storage::LiteralStorage;
196/// # use smallvec::SmallVec;
197/// # use crate::sat::assignment::{Assignment, VecAssignment};
198/// # use crate::sat::variable_selection::{VariableSelection, Vsids};
199/// # use crate::sat::propagation::{Propagator, WatchedLiterals};
200/// # use crate::sat::restarter::{Restarter, Fixed};
201/// # use crate::sat::clause_management::{ClauseManagement, NoClauseManagement};
202/// # use std::fmt::Debug;
203/// # use std::hash::Hash;
204///
205/// solver_config!(
206///     <L: Literal, S: LiteralStorage<L>, A: Assignment>
207///     GenericTestConfig {
208///         Literal = L,
209///         LiteralStorage = S,
210///         Assignment = A,
211///         VariableSelector = Vsids<L>,
212///         Propagator = WatchedLiterals<L, S, A>,
213///         Restarter = Fixed<100>,
214///         ClauseManager = NoClauseManagement<L, S>,
215///     }
216/// );
217/// ```
218#[macro_export]
219macro_rules! solver_config {
220    ($name:ident {
221        Literal = $literal:ty,
222        LiteralStorage = $storage:ty,
223        Assignment = $assignment:ty,
224        VariableSelector = $selector:ty,
225        Propagator = $propagator:ty,
226        Restarter = $restarter:ty,
227        ClauseManager = $manager:ty $(,)?
228    }) => {
229        /// Generated solver configuration.
230        #[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
231        pub struct $name;
232
233        impl SolverConfig for $name {
234            type Literal = $literal;
235            type LiteralStorage = $storage;
236            type Assignment = $assignment;
237            type VariableSelector = $selector;
238            type Propagator = $propagator;
239            type Restarter = $restarter;
240            type ClauseManager = $manager;
241        }
242    };
243
244    (
245        <$($param:ident $(: $bound:path)?),* $(,)?>
246        $name:ident {
247            Literal = $literal:ty,
248            LiteralStorage = $storage:ty,
249            Assignment = $assignment:ty,
250            VariableSelector = $selector:ty,
251            Propagator = $propagator:ty,
252            Restarter = $restarter:ty,
253            ClauseManager = $manager:ty $(,)?
254        }
255    ) => {
256        /// Generated solver configuration with generic parameters.
257        #[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
258        pub struct $name<$($param $(: $bound)?),*> {
259            _marker: std::marker::PhantomData<($($param,)*)>,
260        }
261
262        impl<$($param $(: $bound)?),*> SolverConfig for $name<$($param),*> {
263            type Literal = $literal;
264            type LiteralStorage = $storage;
265            type Assignment = $assignment;
266            type VariableSelector = $selector;
267            type Propagator = $propagator;
268            type Restarter = $restarter;
269            type ClauseManager = $manager;
270        }
271    };
272}
273
274use crate::sat::cdcl::Cdcl;
275use crate::sat::clause_storage::{LiteralStorage, LiteralStorageImpls};
276use crate::sat::dpll::Dpll;
277pub use solver_config;
278
279solver_config!(
280    DefaultConfig {
281        Literal = PackedLiteral,
282        LiteralStorage = SmallVec<[PackedLiteral; 8]>,
283        Assignment = VecAssignment,
284        VariableSelector = Vsids,
285        Propagator = WatchedLiterals<PackedLiteral, SmallVec<[PackedLiteral; 8]>, VecAssignment>,
286        Restarter = Luby<2>,
287        ClauseManager = LbdClauseManagement<10>,
288    }
289);
290
291solver_config!(
292    DynamicConfig {
293        Literal = LiteralImpls,
294        LiteralStorage = LiteralStorageImpls<LiteralImpls, 12>,
295        Assignment = AssignmentImpls,
296        VariableSelector = VariableSelectionImpls,
297        Propagator = PropagatorImpls<LiteralImpls, LiteralStorageImpls<LiteralImpls, 12>, AssignmentImpls>,
298        Restarter = RestarterImpls<3>,
299        ClauseManager = ClauseManagementImpls<10>,
300    }
301);
302
303/// A trait that defines the general interface for a SAT solver.
304///
305/// This trait is generic over a configuration `C` which must implement `SolverConfig`.
306/// This allows different solver implementations to adhere to a common API while being
307/// configurable with various underlying strategies and data structures.
308pub trait Solver<C: SolverConfig = DefaultConfig> {
309    /// Creates a new instance of the solver, initialised with the given CNF formula.
310    ///
311    /// # Arguments
312    ///
313    /// * `cnf`: The `Cnf` formula to be solved.
314    fn new<L: Literal, S: LiteralStorage<L>>(cnf: Cnf<L, S>) -> Self;
315
316    /// Creates a solver instance from its components.
317    ///
318    /// # Arguments
319    ///
320    /// * `cnf`: The `Cnf` formula to be solved.
321    /// * `assignment`: The assignment data structure.
322    /// * `manager`: The clause management scheme.
323    /// * `propagator`: The unit propagation scheme.
324    /// * `restarter`: The restart strategy.
325    /// * `selector`: The variable selection strategy.
326    fn from_parts<L: Literal, S: LiteralStorage<L>>(
327        cnf: Cnf<L, S>,
328        assignment: C::Assignment,
329        manager: C::ClauseManager,
330        propagator: C::Propagator,
331        restarter: C::Restarter,
332        selector: C::VariableSelector,
333    ) -> Self;
334
335    /// Attempts to solve the CNF formula provided at construction.
336    ///
337    /// # Returns
338    ///
339    /// - `Some(Solutions)` if a satisfying assignment (model) is found.
340    /// - `None` if the formula is determined to be unsatisfiable.
341    fn solve(&mut self) -> Option<Solutions>;
342
343    /// Returns the current satisfying assignment if one has been found.
344    ///
345    /// If `solve()` has not been called, or if it returned `None` (unsatisfiable),
346    /// the returned `Solutions` object might be empty or reflect some solver-internal state.
347    /// It's typically called after `solve()` returns `Some`.
348    fn solutions(&self) -> Solutions;
349
350    /// Returns statistics about the solver's last execution of `solve()`.
351    fn stats(&self) -> SolutionStats;
352
353    /// Provides a way to debug the solver's internal state.
354    /// The exact output or behavior is implementation-defined.
355    #[allow(dead_code)]
356    fn debug(&mut self);
357}
358
359/// An enum representing different implementations of SAT solvers.
360#[derive(Debug, Clone)]
361pub enum SolverImpls<C: SolverConfig = DynamicConfig> {
362    /// A DPLL-based SAT solver.
363    Dpll(Box<Dpll<C>>),
364    /// A CDCL-based SAT solver.
365    Cdcl(Box<Cdcl<C>>),
366}
367
368impl<C: SolverConfig> Solver<C> for SolverImpls<C> {
369    fn new<L: Literal, S: LiteralStorage<L>>(cnf: Cnf<L, S>) -> Self {
370        let cnf: Cnf<C::Literal, C::LiteralStorage> = cnf.convert();
371        let cdcl = Cdcl::new(cnf);
372        Self::Cdcl(Box::new(cdcl))
373    }
374
375    fn from_parts<L: Literal, S: LiteralStorage<L>>(
376        cnf: Cnf<L, S>,
377        assignment: C::Assignment,
378        manager: C::ClauseManager,
379        propagator: C::Propagator,
380        restarter: C::Restarter,
381        selector: C::VariableSelector,
382    ) -> Self {
383        let cnf: Cnf<C::Literal, C::LiteralStorage> = cnf.convert();
384        let cdcl = Cdcl::from_parts(cnf, assignment, manager, propagator, restarter, selector);
385        Self::Cdcl(Box::new(cdcl))
386    }
387
388    fn solve(&mut self) -> Option<Solutions> {
389        match self {
390            Self::Dpll(solver) => solver.solve(),
391            Self::Cdcl(solver) => solver.solve(),
392        }
393    }
394
395    fn solutions(&self) -> Solutions {
396        match self {
397            Self::Dpll(solver) => solver.solutions(),
398            Self::Cdcl(solver) => solver.solutions(),
399        }
400    }
401
402    fn stats(&self) -> SolutionStats {
403        match self {
404            Self::Dpll(solver) => solver.stats(),
405            Self::Cdcl(solver) => solver.stats(),
406        }
407    }
408
409    fn debug(&mut self) {
410        match self {
411            Self::Dpll(solver) => solver.debug(),
412            Self::Cdcl(solver) => solver.debug(),
413        }
414    }
415}
416
417/// An enum representing the types of SAT solvers available.
418#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, ValueEnum)]
419pub enum SolverType {
420    /// A DPLL-based SAT solver.
421    Dpll,
422    /// A CDCL-based SAT solver.
423    #[default]
424    Cdcl,
425}
426
427impl Display for SolverType {
428    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
429        match self {
430            Self::Dpll => write!(f, "dpll"),
431            Self::Cdcl => write!(f, "cdcl"),
432        }
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439    use crate::sat::variable_selection::VariableSelection as VSelTrait;
440
441    #[test]
442    fn test_generic_config_instantiation() {
443        let _ = DefaultConfig;
444    }
445
446    #[test]
447    fn test_default_config_type_bounds() {
448        let _ = DefaultConfig;
449        let _vs: <DefaultConfig as SolverConfig>::VariableSelector =
450            Vsids::new::<Vec<PackedLiteral>>(10, &[], &[]);
451    }
452
453    #[test]
454    fn test_solutions_display() {
455        let s1 = Solutions::new(&[1, -2, 30]);
456        assert_eq!(s1.to_string(), "1 -2 30");
457
458        let mut assignments_vec: Vec<String> = s1
459            .assignments
460            .iter()
461            .map(|val| val.get().to_string())
462            .collect();
463        assignments_vec.sort_by_key(|s| s.parse::<i32>().unwrap_or(0));
464        let sorted_str = assignments_vec.join(" ");
465
466        assert_eq!(sorted_str, "-2 1 30");
467
468        let s2 = Solutions::new(&[]);
469        assert_eq!(s2.to_string(), "");
470
471        let s3 = Solutions::new(&[-5]);
472        assert_eq!(s3.to_string(), "-5");
473    }
474
475    #[test]
476    fn test_solutions_check_add() {
477        let mut s = Solutions::default();
478        let lit1_pos = NonZeroI32::new(1).unwrap();
479        let lit2_neg = NonZeroI32::new(-2).unwrap();
480        let lit3_pos = NonZeroI32::new(3).unwrap();
481
482        assert!(!s.check(lit1_pos));
483        s.add(lit1_pos);
484        assert!(s.check(lit1_pos));
485        assert!(!s.check(lit1_pos.checked_neg().unwrap()));
486
487        s.add(lit2_neg);
488        assert!(s.check(lit2_neg));
489        assert!(!s.check(lit3_pos));
490
491        assert_eq!(s.len(), 2);
492        assert!(!s.is_empty());
493    }
494}