#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
use crate::sat::assignment::{AssignmentImpls, VecAssignment};
use crate::sat::clause_management::{ClauseManagementImpls, LbdClauseManagement};
use crate::sat::cnf::Cnf;
use crate::sat::literal::{Literal, LiteralImpls, PackedLiteral};
use crate::sat::propagation::{PropagatorImpls, WatchedLiterals};
use crate::sat::restarter::{Luby, RestarterImpls};
use crate::sat::variable_selection::{VariableSelectionImpls, Vsids};
use clap::ValueEnum;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use smallvec::SmallVec;
use std::fmt::{Debug, Display, Formatter};
use std::num::NonZeroI32;
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SolutionStats {
pub conflicts: usize,
pub decisions: usize,
pub propagations: usize,
pub restarts: usize,
pub learnt_clauses: usize,
pub removed_clauses: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Solutions {
pub assignments: FxHashSet<NonZeroI32>,
}
impl Display for Solutions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let assignments_str: Vec<String> = self
.assignments
.iter()
.sorted_by(|left, right| Ord::cmp(&left.abs(), &right.abs()))
.map(|&val| val.get().to_string())
.collect();
write!(f, "{}", assignments_str.join(" "))
}
}
impl Solutions {
#[must_use]
pub fn new(s: &[i32]) -> Self {
Self {
assignments: s.iter().copied().filter_map(NonZeroI32::new).collect(),
}
}
pub fn iter(&self) -> impl Iterator<Item = &NonZeroI32> {
self.assignments.iter()
}
#[must_use]
pub fn check(&self, i: NonZeroI32) -> bool {
self.assignments.contains(&i)
}
#[must_use]
pub fn len(&self) -> usize {
self.assignments.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.assignments.is_empty()
}
pub fn add(&mut self, i: NonZeroI32) {
self.assignments.insert(i);
}
}
pub trait SolverConfig: Debug + Clone {
type Assignment: crate::sat::assignment::Assignment;
type VariableSelector: crate::sat::variable_selection::VariableSelection<Self::Literal>;
type Literal: Literal;
type LiteralStorage: LiteralStorage<Self::Literal>;
type Restarter: crate::sat::restarter::Restarter;
type Propagator: crate::sat::propagation::Propagator<Self::Literal, Self::LiteralStorage, Self::Assignment>;
type ClauseManager: crate::sat::clause_management::ClauseManagement;
}
#[macro_export]
macro_rules! solver_config {
($name:ident {
Literal = $literal:ty,
LiteralStorage = $storage:ty,
Assignment = $assignment:ty,
VariableSelector = $selector:ty,
Propagator = $propagator:ty,
Restarter = $restarter:ty,
ClauseManager = $manager:ty $(,)?
}) => {
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name;
impl SolverConfig for $name {
type Literal = $literal;
type LiteralStorage = $storage;
type Assignment = $assignment;
type VariableSelector = $selector;
type Propagator = $propagator;
type Restarter = $restarter;
type ClauseManager = $manager;
}
};
(
<$($param:ident $(: $bound:path)?),* $(,)?>
$name:ident {
Literal = $literal:ty,
LiteralStorage = $storage:ty,
Assignment = $assignment:ty,
VariableSelector = $selector:ty,
Propagator = $propagator:ty,
Restarter = $restarter:ty,
ClauseManager = $manager:ty $(,)?
}
) => {
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name<$($param $(: $bound)?),*> {
_marker: std::marker::PhantomData<($($param,)*)>,
}
impl<$($param $(: $bound)?),*> SolverConfig for $name<$($param),*> {
type Literal = $literal;
type LiteralStorage = $storage;
type Assignment = $assignment;
type VariableSelector = $selector;
type Propagator = $propagator;
type Restarter = $restarter;
type ClauseManager = $manager;
}
};
}
use crate::sat::cdcl::Cdcl;
use crate::sat::clause_storage::{LiteralStorage, LiteralStorageImpls};
use crate::sat::dpll::Dpll;
pub use solver_config;
solver_config!(
DefaultConfig {
Literal = PackedLiteral,
LiteralStorage = SmallVec<[PackedLiteral; 8]>,
Assignment = VecAssignment,
VariableSelector = Vsids,
Propagator = WatchedLiterals<PackedLiteral, SmallVec<[PackedLiteral; 8]>, VecAssignment>,
Restarter = Luby<2>,
ClauseManager = LbdClauseManagement<10>,
}
);
solver_config!(
DynamicConfig {
Literal = LiteralImpls,
LiteralStorage = LiteralStorageImpls<LiteralImpls, 12>,
Assignment = AssignmentImpls,
VariableSelector = VariableSelectionImpls,
Propagator = PropagatorImpls<LiteralImpls, LiteralStorageImpls<LiteralImpls, 12>, AssignmentImpls>,
Restarter = RestarterImpls<3>,
ClauseManager = ClauseManagementImpls<10>,
}
);
pub trait Solver<C: SolverConfig = DefaultConfig> {
fn new<L: Literal, S: LiteralStorage<L>>(cnf: Cnf<L, S>) -> Self;
fn from_parts<L: Literal, S: LiteralStorage<L>>(
cnf: Cnf<L, S>,
assignment: C::Assignment,
manager: C::ClauseManager,
propagator: C::Propagator,
restarter: C::Restarter,
selector: C::VariableSelector,
) -> Self;
fn solve(&mut self) -> Option<Solutions>;
fn solutions(&self) -> Solutions;
fn stats(&self) -> SolutionStats;
#[allow(dead_code)]
fn debug(&mut self);
}
#[derive(Debug, Clone)]
pub enum SolverImpls<C: SolverConfig = DynamicConfig> {
Dpll(Box<Dpll<C>>),
Cdcl(Box<Cdcl<C>>),
}
impl<C: SolverConfig> Solver<C> for SolverImpls<C> {
fn new<L: Literal, S: LiteralStorage<L>>(cnf: Cnf<L, S>) -> Self {
let cnf: Cnf<C::Literal, C::LiteralStorage> = cnf.convert();
let cdcl = Cdcl::new(cnf);
Self::Cdcl(Box::new(cdcl))
}
fn from_parts<L: Literal, S: LiteralStorage<L>>(
cnf: Cnf<L, S>,
assignment: C::Assignment,
manager: C::ClauseManager,
propagator: C::Propagator,
restarter: C::Restarter,
selector: C::VariableSelector,
) -> Self {
let cnf: Cnf<C::Literal, C::LiteralStorage> = cnf.convert();
let cdcl = Cdcl::from_parts(cnf, assignment, manager, propagator, restarter, selector);
Self::Cdcl(Box::new(cdcl))
}
fn solve(&mut self) -> Option<Solutions> {
match self {
Self::Dpll(solver) => solver.solve(),
Self::Cdcl(solver) => solver.solve(),
}
}
fn solutions(&self) -> Solutions {
match self {
Self::Dpll(solver) => solver.solutions(),
Self::Cdcl(solver) => solver.solutions(),
}
}
fn stats(&self) -> SolutionStats {
match self {
Self::Dpll(solver) => solver.stats(),
Self::Cdcl(solver) => solver.stats(),
}
}
fn debug(&mut self) {
match self {
Self::Dpll(solver) => solver.debug(),
Self::Cdcl(solver) => solver.debug(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, ValueEnum)]
pub enum SolverType {
Dpll,
#[default]
Cdcl,
}
impl Display for SolverType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Dpll => write!(f, "dpll"),
Self::Cdcl => write!(f, "cdcl"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sat::variable_selection::VariableSelection as VSelTrait;
#[test]
fn test_generic_config_instantiation() {
let _ = DefaultConfig;
}
#[test]
fn test_default_config_type_bounds() {
let _ = DefaultConfig;
let _vs: <DefaultConfig as SolverConfig>::VariableSelector =
Vsids::new::<Vec<PackedLiteral>>(10, &[], &[]);
}
#[test]
fn test_solutions_display() {
let s1 = Solutions::new(&[1, -2, 30]);
assert_eq!(s1.to_string(), "1 -2 30");
let mut assignments_vec: Vec<String> = s1
.assignments
.iter()
.map(|val| val.get().to_string())
.collect();
assignments_vec.sort_by_key(|s| s.parse::<i32>().unwrap_or(0));
let sorted_str = assignments_vec.join(" ");
assert_eq!(sorted_str, "-2 1 30");
let s2 = Solutions::new(&[]);
assert_eq!(s2.to_string(), "");
let s3 = Solutions::new(&[-5]);
assert_eq!(s3.to_string(), "-5");
}
#[test]
fn test_solutions_check_add() {
let mut s = Solutions::default();
let lit1_pos = NonZeroI32::new(1).unwrap();
let lit2_neg = NonZeroI32::new(-2).unwrap();
let lit3_pos = NonZeroI32::new(3).unwrap();
assert!(!s.check(lit1_pos));
s.add(lit1_pos);
assert!(s.check(lit1_pos));
assert!(!s.check(lit1_pos.checked_neg().unwrap()));
s.add(lit2_neg);
assert!(s.check(lit2_neg));
assert!(!s.check(lit3_pos));
assert_eq!(s.len(), 2);
assert!(!s.is_empty());
}
}