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}