#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
mod binding {
include!("../../bindings/picosat_bindings.rs");
}
use std::{collections::HashSet, fmt::Display, os::raw, ptr::NonNull};
use crate::{errors::SolverError, solver::RawStatus};
use super::{MusSolver, MusStatus, SatSolver};
macro_rules! ffi_bind {
(
$(#[$doc:meta])*
$c_name:ident ($($arg:ident: $arg_ty:ty),*) -> $ret:ty;
as $rust_name:ident
) => {
$(#[$doc])*
pub fn $rust_name(&mut self, $($arg: $arg_ty),*) -> Result<$ret, SolverError> {
unsafe {
let ret = binding::$c_name(self.inner.as_ptr() $(, $arg.into())*);
self.error()?;
Ok(ret.into())
}
}
};
(
$(#[$doc:meta])*
$c_name:ident ($($arg:ident: $arg_ty:ty),*) -> $ret:ty => |$raw_var:ident| $convert:expr;
as $rust_name:ident
) => {
$(#[$doc])*
pub fn $rust_name(&mut self, $($arg: $arg_ty),*) -> Result<$ret, SolverError> {
unsafe {
let $raw_var = binding::$c_name(self.inner.as_ptr() $(, $arg.into())*);
self.error()?;
Ok($convert)
}
}
};
}
fn ptr_to_vec<T: Display + PartialEq + std::cmp::PartialEq<i32>>(ptr: *const T) -> Vec<T> {
let mut vec = Vec::new();
let mut curr = ptr;
let mut v = unsafe { curr.read() };
while !curr.is_null() && v != 0 {
unsafe {
vec.push(v);
curr = curr.offset(1);
v = curr.read();
}
}
vec
}
#[derive(Debug, Clone)]
pub struct PicoSATSolver {
inner: NonNull<binding::PicoSATSolver>,
clauses: Vec<Vec<i32>>,
vars: i32,
}
impl Default for PicoSATSolver {
fn default() -> Self {
Self::new()
}
}
impl PicoSATSolver {
pub fn new() -> Self {
unsafe {
PicoSATSolver {
inner: NonNull::new(binding::picosat_s_init()).unwrap(),
clauses: Vec::new(),
vars: 0,
}
}
}
fn error(&self) -> Result<(), SolverError> {
unsafe {
let code = binding::picosat_s_error(self.inner.as_ptr());
if code != 0 {
let msg = binding::picosat_s_errmsg(code);
let msg = std::ffi::CStr::from_ptr(msg);
return Err(SolverError(msg.to_str().unwrap()));
}
}
Ok(())
}
fn add_inner_clause(&self, clause: &[i32]) -> Result<(), SolverError> {
unsafe {
binding::picosat_s_add_lits(self.inner.as_ptr(), clause.as_ptr(), clause.len());
}
self.error()?;
Ok(())
}
fn add_inner(&self, lit: i32) -> Result<(), SolverError> {
unsafe {
binding::picosat_s_add(self.inner.as_ptr(), lit);
}
self.error()?;
Ok(())
}
ffi_bind! {
picosat_s_measure_all_calls () -> ();
as measure_all_calls
}
ffi_bind! {
picosat_s_set_prefix (prefix: *const raw::c_char) -> ();
as set_prefix
}
ffi_bind! {
picosat_s_set_verbosity (new_verbosity_level: i32) -> ();
as set_verbosity
}
ffi_bind! {
picosat_s_set_plain (new_plain_value: i32) -> ();
as set_plain
}
ffi_bind! {
picosat_s_set_global_default_phase (phase: i32) -> ();
as set_global_default_phase
}
ffi_bind! {
picosat_s_set_default_phase_lit (lit: i32, phase: i32) -> ();
as set_default_phase_lit
}
ffi_bind! {
picosat_s_reset_phases () -> ();
as reset_phases
}
ffi_bind! {
picosat_s_reset_scores () -> ();
as reset_scores
}
ffi_bind! {
picosat_s_remove_learned (percentage: u32) -> ();
as remove_learned
}
ffi_bind! {
picosat_s_set_more_important_lit (lit: i32) -> ();
as set_more_important_lit
}
ffi_bind! {
picosat_s_set_less_important_lit (lit: i32) -> ();
as set_less_important_lit
}
ffi_bind! {
picosat_s_set_seed (random_number_generator_seed: u32) -> ();
as set_seed
}
ffi_bind! {
picosat_s_enable_trace_generation () -> bool => |res| res != 0;
as enable_trace_generation
}
ffi_bind! {
picosat_s_save_original_clauses () -> ();
as save_original_clauses
}
ffi_bind! {
picosat_s_inc_max_var () -> i32;
as inc_max_var
}
ffi_bind! {
picosat_s_push () -> i32;
as push
}
ffi_bind! {
picosat_s_failed_context (lit: i32) -> bool => |res| res != 0;
as failed_context
}
ffi_bind! {
picosat_s_context () -> i32;
as context
}
ffi_bind! {
picosat_s_pop () -> i32;
as pop
}
ffi_bind! {
picosat_s_simplify () -> ();
as simplify
}
ffi_bind! {
picosat_s_adjust (max_idx: i32) -> ();
as adjust
}
ffi_bind! {
picosat_s_variables () -> i32;
as variables
}
ffi_bind! {
picosat_s_added_original_clauses () -> i32;
as added_original_clauses
}
ffi_bind! {
picosat_s_max_bytes_allocated () -> usize;
as max_bytes_allocated
}
ffi_bind! {
picosat_s_stats () -> ();
as stats
}
ffi_bind! {
picosat_s_propagations () -> u64;
as propagations
}
ffi_bind! {
picosat_s_decisions () -> u64;
as decisions
}
ffi_bind! {
picosat_s_visits () -> u64;
as visits
}
ffi_bind! {
picosat_s_seconds () -> f64;
as seconds
}
ffi_bind! {
picosat_s_add (lit: i32) -> i32;
as add
}
ffi_bind! {
picosat_s_assume (lit: i32) -> ();
as assume
}
ffi_bind! {
picosat_s_add_ado_lit (lit: i32) -> ();
as add_ado_lit
}
ffi_bind! {
picosat_s_sat (decision_limit: i32) -> RawStatus;
as sat
}
ffi_bind! {
picosat_s_set_propagation_limit (limit: u64) -> ();
as set_propagation_limit
}
ffi_bind! {
picosat_s_res () -> RawStatus;
as res
}
ffi_bind! {
picosat_s_deref (lit: i32) -> Option<bool> => |res| {
match res {
1 => Some(true),
-1 => Some(false),
_ => None,
}
};
as deref
}
ffi_bind! {
picosat_s_deref_toplevel (lit: i32) -> Option<bool> => |res| {
match res {
1 => Some(true),
-1 => Some(false),
_ => None,
}
};
as deref_toplevel
}
ffi_bind! {
picosat_s_deref_partial (lit: i32) -> Option<bool> => |res| {
match res {
1 => Some(true),
-1 => Some(false),
_ => None,
}
};
as deref_partial
}
ffi_bind! {
picosat_s_inconsistent () -> bool => |res| res != 0;
as is_inconsistent
}
ffi_bind! {
picosat_s_failed_assumption (lit: i32) -> bool => |res| res != 0;
as failed_assumption
}
ffi_bind! {
picosat_s_failed_assumptions () -> Vec<i32> => |res| {
ptr_to_vec(res)
};
as failed_assumptions
}
ffi_bind! {
picosat_s_mus_assumptions (
state: *mut raw::c_void,
callback: Option<unsafe extern "C" fn(*mut raw::c_void, *const i32)>,
fix: i32
) -> Vec<i32> =>|res|ptr_to_vec(res);
as mus_assumptions
}
ffi_bind! {
picosat_s_maximal_satisfiable_subset_of_assumptions () -> *const i32;
as maximal_satisfiable_subset_of_assumptions
}
ffi_bind! {
picosat_s_next_maximal_satisfiable_subset_of_assumptions () -> *const i32;
as next_maximal_satisfiable_subset_of_assumptions
}
ffi_bind! {
picosat_s_next_minimal_correcting_subset_of_assumptions () -> *const i32;
as next_minimal_correcting_subset_of_assumptions
}
ffi_bind! {
picosat_s_humus (
callback: Option<unsafe extern "C" fn(*mut raw::c_void, i32, i32)>,
state: *mut raw::c_void
) -> Vec<i32> =>|res|ptr_to_vec(res);
as humus
}
ffi_bind! {
picosat_s_changed () -> bool => |res| res != 0;
as changed
}
ffi_bind! {
picosat_s_coreclause (i: i32) -> bool => |res| res != 0;
as coreclause
}
ffi_bind! {
picosat_s_corelit (lit: i32) -> bool => |res| res != 0;
as corelit
}
ffi_bind! {
picosat_s_usedlit (lit: i32) -> bool => |res| res != 0;
as usedlit
}
ffi_bind! {
picosat_s_enter ()->(); as enter
}
ffi_bind! {
picosat_s_leave ()->(); as leave
}
fn core_extraction(
&mut self,
max_rounds: i32,
min_rounds: i32,
max_non_red_rounds: i32,
redundant_indices: &mut HashSet<usize>,
) -> Result<RawStatus, SolverError> {
let mut non_red_count = 0;
let mut n = self.clauses.len();
for round in 1..=max_rounds {
self.set_seed(round as u32)?;
for (i, clause) in self.clauses.iter().enumerate() {
if redundant_indices.contains(&i) {
self.add_inner_clause(&[1, -1])?;
} else {
self.add_inner_clause(clause)?;
}
}
let res: RawStatus = self.sat(-1)?;
if res == RawStatus::Satisfiable {
return Ok(res);
}
for i in 0..self.clauses.len() {
if redundant_indices.contains(&i) {
continue;
}
if !self.coreclause(i as i32)? {
redundant_indices.insert(i);
}
}
let old_n = n;
n = self.clauses.len() - redundant_indices.len();
unsafe {
binding::picosat_s_reset(self.inner.as_ptr());
self.inner = NonNull::new(binding::picosat_s_init()).unwrap();
}
if round >= min_rounds {
let red = old_n - n;
if red < 10 && (100 * red + 99) / old_n < 2 {
non_red_count += 1;
if non_red_count > max_non_red_rounds {
break;
}
} else {
non_red_count = 0;
}
if non_red_count > max_non_red_rounds {
break;
}
}
if round < max_rounds {
self.enable_trace_generation()?;
}
}
Ok(RawStatus::Unsatisfiable)
}
}
impl SatSolver for PicoSATSolver {
fn push_clause(&mut self, clause: &[i32]) -> Result<(), SolverError> {
self.add_inner_clause(clause)
}
fn solve_sat(&mut self) -> Result<RawStatus, SolverError> {
self.sat(-1)
}
fn model(&mut self) -> Result<Vec<i32>, SolverError> {
let mut model = Vec::new();
for v in 0..self.variables()? {
let lit = v + 1;
let assum = self.deref(lit)?;
if let Some(true) = assum {
model.push(lit);
}
}
Ok(model)
}
}
impl MusSolver for PicoSATSolver {
fn push_clause(&mut self, clause: &[i32]) -> Result<(), SolverError> {
self.vars = clause
.iter()
.map(|lit| lit.abs())
.max()
.unwrap_or(0)
.max(self.vars);
self.clauses.push(clause.to_vec());
Ok(())
}
fn solve_mus(&mut self) -> Result<MusStatus, SolverError> {
let mut redundant_indices: HashSet<usize> = HashSet::new();
if self.enable_trace_generation()? {
let res = self.core_extraction(100, 3, 3, &mut redundant_indices)?;
if res == RawStatus::Satisfiable {
return Ok(MusStatus::Satisfiable);
}
}
let idx: i32 = self.vars + 1;
for (i, clause) in self.clauses.iter().enumerate() {
if redundant_indices.contains(&i) {
continue;
}
self.add_inner(-(idx + i as i32))?;
self.add_inner_clause(&clause)?;
}
for i in 0..self.clauses.len() {
if !redundant_indices.contains(&i) {
self.assume(idx + i as i32)?;
}
}
let status = self.sat(-1)?;
match status {
RawStatus::Satisfiable => Ok(MusStatus::Satisfiable),
RawStatus::Unsatisfiable => {
redundant_indices.extend(0..self.clauses.len());
let mut mus = unsafe {
binding::picosat_s_mus_assumptions(
self.inner.as_ptr(),
std::ptr::null_mut(),
None,
1,
)
};
self.error()?;
while !mus.is_null() {
let assumption = unsafe { *mus };
if assumption == 0 {
break;
}
redundant_indices.remove(&((assumption - idx) as usize));
mus = unsafe { mus.offset(1) };
}
let mus: Vec<usize> = (0..self.clauses.len())
.filter(|i| !redundant_indices.contains(&i))
.collect::<Vec<usize>>();
Ok(MusStatus::Unsatisfiable(mus))
}
RawStatus::Unknown => Ok(MusStatus::Unknown),
}
}
}
impl Drop for PicoSATSolver {
fn drop(&mut self) {
unsafe {
binding::picosat_s_reset(self.inner.as_ptr());
}
}
}
#[cfg(test)]
mod tests {
use crate::solver::SatSolver;
use crate::solver::SatStatus;
use super::*;
#[test]
fn unsat() {
let mut solver = PicoSATSolver::new();
SatSolver::push_clause(&mut solver, &vec![1]).unwrap();
SatSolver::push_clause(&mut solver, &vec![-1]).unwrap();
assert!(matches!(
solver.solve_model().unwrap(),
SatStatus::Unsatisfiable
));
}
#[test]
fn sat() {
let mut solver = PicoSATSolver::new();
SatSolver::push_clause(&mut solver, &vec![1, 2]).unwrap();
SatSolver::push_clause(&mut solver, &vec![-1]).unwrap();
assert!(
matches!(solver.solve_model().unwrap(),SatStatus::Satisfiable(x) if x.eq(&vec![2]))
);
}
}