pub use crate::{
assign::AssignReason,
cdb::{Clause, ClauseDB, ClauseIF, ClauseId, ClauseIdIF},
config::Config,
primitive::{ema::*, luby::*},
solver::SolverEvent,
};
use std::{
cmp::Ordering,
fmt,
fs::File,
io::{BufRead, BufReader},
num::NonZeroU32,
ops::{Index, IndexMut, Not},
path::Path,
};
pub trait PropertyReference<I, O> {
fn refer(&self, key: I) -> &O;
}
pub trait PropertyDereference<I, O: Sized> {
fn derefer(&self, key: I) -> O;
}
pub trait LitIF {
fn as_bool(&self) -> bool;
fn vi(self) -> VarId;
}
pub trait ActivityIF<Ix> {
fn activity(&self, ix: Ix) -> f64;
fn set_activity(&mut self, ix: Ix, val: f64);
fn reward_at_analysis(&mut self, _ix: Ix) {
#[cfg(debug)]
todo!()
}
fn reward_at_assign(&mut self, _ix: Ix) {
#[cfg(debug)]
todo!()
}
fn reward_at_propagation(&mut self, _ix: Ix) {
#[cfg(debug)]
todo!()
}
fn reward_at_unassign(&mut self, _ix: Ix) {
#[cfg(debug)]
todo!()
}
fn update_activity_decay(&mut self, _decay: f64);
fn update_activity_tick(&mut self);
}
pub trait Instantiate {
fn instantiate(conf: &Config, cnf: &CNFDescription) -> Self;
fn handle(&mut self, _e: SolverEvent) {}
}
pub trait Delete<T> {
fn delete_unstable<F>(&mut self, filter: F)
where
F: FnMut(&T) -> bool;
}
pub type VarId = usize;
pub type DecisionLevel = u32;
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Lit {
ordinal: NonZeroU32,
}
impl fmt::Display for Lit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}L", i32::from(self))
}
}
impl fmt::Debug for Lit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}L", i32::from(self))
}
}
pub fn i32s(v: &[Lit]) -> Vec<i32> {
v.iter().map(|l| i32::from(*l)).collect::<Vec<_>>()
}
impl From<(VarId, bool)> for Lit {
#[inline]
fn from((vi, b): (VarId, bool)) -> Self {
Lit {
ordinal: unsafe { NonZeroU32::new_unchecked(((vi as u32) << 1) + (b as u32)) },
}
}
}
impl From<usize> for Lit {
#[inline]
fn from(l: usize) -> Self {
Lit {
ordinal: unsafe { NonZeroU32::new_unchecked(l as u32) },
}
}
}
impl From<u32> for Lit {
#[inline]
fn from(l: u32) -> Self {
Lit {
ordinal: unsafe { NonZeroU32::new_unchecked(l) },
}
}
}
impl From<i32> for Lit {
#[inline]
fn from(x: i32) -> Self {
Lit {
ordinal: unsafe {
NonZeroU32::new_unchecked((if x < 0 { -2 * x } else { 2 * x + 1 }) as u32)
},
}
}
}
impl From<ClauseId> for Lit {
#[inline]
fn from(cid: ClauseId) -> Self {
Lit {
ordinal: unsafe {
NonZeroU32::new_unchecked(NonZeroU32::get(cid.ordinal) & 0x7FFF_FFFF)
},
}
}
}
impl From<Lit> for bool {
#[inline]
fn from(l: Lit) -> bool {
(NonZeroU32::get(l.ordinal) & 1) != 0
}
}
impl From<Lit> for ClauseId {
#[inline]
fn from(l: Lit) -> ClauseId {
ClauseId {
ordinal: unsafe { NonZeroU32::new_unchecked(NonZeroU32::get(l.ordinal) | 0x8000_0000) },
}
}
}
impl From<Lit> for usize {
#[inline]
fn from(l: Lit) -> usize {
NonZeroU32::get(l.ordinal) as usize
}
}
impl From<Lit> for i32 {
#[inline]
fn from(l: Lit) -> i32 {
if NonZeroU32::get(l.ordinal) % 2 == 0 {
-((NonZeroU32::get(l.ordinal) >> 1) as i32)
} else {
(NonZeroU32::get(l.ordinal) >> 1) as i32
}
}
}
impl From<&Lit> for i32 {
#[inline]
fn from(l: &Lit) -> i32 {
if NonZeroU32::get(l.ordinal) % 2 == 0 {
-((NonZeroU32::get(l.ordinal) >> 1) as i32)
} else {
(NonZeroU32::get(l.ordinal) >> 1) as i32
}
}
}
impl Not for Lit {
type Output = Lit;
#[inline]
fn not(self) -> Self {
Lit {
ordinal: unsafe { NonZeroU32::new_unchecked(NonZeroU32::get(self.ordinal) ^ 1) },
}
}
}
impl Index<Lit> for [bool] {
type Output = bool;
#[inline]
fn index(&self, l: Lit) -> &Self::Output {
#[cfg(feature = "unsafe_access")]
unsafe {
self.get_unchecked(usize::from(l))
}
#[cfg(not(feature = "unsafe_access"))]
&self[usize::from(l)]
}
}
impl IndexMut<Lit> for [bool] {
#[inline]
fn index_mut(&mut self, l: Lit) -> &mut Self::Output {
#[cfg(feature = "unsafe_access")]
unsafe {
self.get_unchecked_mut(usize::from(l))
}
#[cfg(not(feature = "unsafe_access"))]
&mut self[usize::from(l)]
}
}
impl Index<Lit> for Vec<bool> {
type Output = bool;
#[inline]
fn index(&self, l: Lit) -> &Self::Output {
#[cfg(feature = "unsafe_access")]
unsafe {
self.get_unchecked(usize::from(l))
}
#[cfg(not(feature = "unsafe_access"))]
&self[usize::from(l)]
}
}
impl IndexMut<Lit> for Vec<bool> {
#[inline]
fn index_mut(&mut self, l: Lit) -> &mut Self::Output {
#[cfg(feature = "unsafe_access")]
unsafe {
self.get_unchecked_mut(usize::from(l))
}
#[cfg(not(feature = "unsafe_access"))]
&mut self[usize::from(l)]
}
}
impl LitIF for Lit {
#[inline]
fn as_bool(&self) -> bool {
NonZeroU32::get(self.ordinal) & 1 == 1
}
#[inline]
fn vi(self) -> VarId {
(NonZeroU32::get(self.ordinal) >> 1) as VarId
}
}
pub type ConflictContext = (Lit, AssignReason);
pub type PropagationResult = Result<(), ConflictContext>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RefClause {
Clause(ClauseId),
Dead,
EmptyClause,
RegisteredClause(ClauseId),
UnitClause(Lit),
}
impl RefClause {
pub fn as_cid(&self) -> ClauseId {
match self {
RefClause::Clause(cid) => *cid,
RefClause::RegisteredClause(cid) => *cid,
_ => panic!("invalid reference to clause"),
}
}
pub fn is_new(&self) -> Option<ClauseId> {
match self {
RefClause::Clause(cid) => Some(*cid),
RefClause::RegisteredClause(_) => None,
RefClause::EmptyClause => None,
RefClause::Dead => None,
RefClause::UnitClause(_) => None,
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum SolverError {
EmptyClause,
InvalidLiteral,
IOError,
Inconsistent,
OutOfMemory,
RootLevelConflict(ConflictContext),
TimeOut,
SolverBug,
UndescribedError,
}
impl fmt::Display for SolverError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
pub type MaybeInconsistent = Result<(), SolverError>;
#[derive(Clone, Debug)]
pub enum CNFIndicator {
Void,
File(String),
LitVec(usize),
}
impl Default for CNFIndicator {
fn default() -> Self {
CNFIndicator::Void
}
}
impl fmt::Display for CNFIndicator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CNFIndicator::Void => write!(f, "No CNF specified)"),
CNFIndicator::File(file) => write!(f, "CNF file({file})"),
CNFIndicator::LitVec(n) => write!(f, "A vec({n} clauses)"),
}
}
}
#[derive(Clone, Debug)]
pub struct CNFDescription {
pub num_of_variables: usize,
pub num_of_clauses: usize,
pub pathname: CNFIndicator,
}
impl Default for CNFDescription {
fn default() -> CNFDescription {
CNFDescription {
num_of_variables: 0,
num_of_clauses: 0,
pathname: CNFIndicator::Void,
}
}
}
impl fmt::Display for CNFDescription {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let CNFDescription {
num_of_variables: nv,
num_of_clauses: nc,
pathname: path,
} = &self;
write!(f, "CNF({nv}, {nc}, {path})")
}
}
impl<V: AsRef<[i32]>> From<&[V]> for CNFDescription {
fn from(vec: &[V]) -> Self {
let num_of_variables = vec
.iter()
.map(|clause| clause.as_ref().iter().map(|l| l.abs()).max().unwrap_or(0))
.max()
.unwrap_or(0) as usize;
CNFDescription {
num_of_variables,
num_of_clauses: vec.len(),
pathname: CNFIndicator::LitVec(vec.len()),
}
}
}
#[derive(Debug)]
pub struct CNFReader {
pub cnf: CNFDescription,
pub reader: BufReader<File>,
}
impl TryFrom<&Path> for CNFReader {
type Error = SolverError;
fn try_from(path: &Path) -> Result<Self, Self::Error> {
let pathname = if path.to_string_lossy().is_empty() {
"--".to_string()
} else {
Path::new(&path.to_string_lossy().into_owned())
.file_name()
.map_or("aStrangeNamed".to_string(), |f| {
f.to_string_lossy().into_owned()
})
};
let fs = File::open(path).map_or(Err(SolverError::IOError), Ok)?;
let mut reader = BufReader::new(fs);
let mut buf = String::new();
let mut nv: usize = 0;
let mut nc: usize = 0;
let mut found_valid_header = false;
loop {
buf.clear();
match reader.read_line(&mut buf) {
Ok(0) => break,
Ok(_k) => {
let mut iter = buf.split_whitespace();
if iter.next() == Some("p") && iter.next() == Some("cnf") {
if let Some(v) = iter.next().map(|s| s.parse::<usize>().ok().unwrap()) {
if let Some(c) = iter.next().map(|s| s.parse::<usize>().ok().unwrap()) {
nv = v;
nc = c;
found_valid_header = true;
break;
}
}
}
continue;
}
Err(e) => {
println!("{e}");
return Err(SolverError::IOError);
}
}
}
if !found_valid_header {
return Err(SolverError::IOError);
}
let cnf = CNFDescription {
num_of_variables: nv,
num_of_clauses: nc,
pathname: CNFIndicator::File(pathname),
};
Ok(CNFReader { cnf, reader })
}
}
impl<T> Delete<T> for Vec<T> {
fn delete_unstable<F>(&mut self, filter: F)
where
F: FnMut(&T) -> bool,
{
if let Some(i) = self.iter().position(filter) {
self.swap_remove(i);
}
}
}
pub trait FlagIF {
type FlagType;
fn is(&self, flag: Self::FlagType) -> bool;
fn set(&mut self, f: Self::FlagType, b: bool);
fn toggle(&mut self, flag: Self::FlagType);
fn turn_off(&mut self, flag: Self::FlagType);
fn turn_on(&mut self, flag: Self::FlagType);
}
bitflags! {
pub struct FlagClause: u8 {
const LEARNT = 0b0000_0001;
const USED = 0b0000_0010;
const ENQUEUED = 0b0000_0100;
const OCCUR_LINKED = 0b0000_1000;
const DERIVE20 = 0b0001_0000;
}
}
bitflags! {
pub struct FlagVar: u8 {
const PHASE = 0b0000_0001;
const USED = 0b0000_0010;
const ELIMINATED = 0b0000_0100;
const ENQUEUED = 0b0000_1000;
const CA_SEEN = 0b0001_0000;
#[cfg(feature = "debug_propagation")]
const PROPAGATED = 0b0010_0000;
}
}
#[derive(Debug, Default)]
pub struct Logger {
dest: Option<File>,
}
impl fmt::Display for Logger {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Dump({:?})", self.dest)
}
}
impl Logger {
pub fn new<T: AsRef<str>>(fname: T) -> Self {
Logger {
dest: File::create(fname.as_ref()).ok(),
}
}
pub fn dump(&mut self, mes: String) {
use std::io::Write;
if let Some(f) = &mut self.dest {
f.write_all(&mes.into_bytes())
.unwrap_or_else(|_| panic!("fail to dump {f:?}"));
} else {
println!("{mes}");
}
}
}
#[derive(Clone, Debug)]
pub struct OrderedProxy<T: Clone + Default + Sized + Ord> {
index: f64,
body: T,
}
impl<T: Clone + Default + Sized + Ord> Default for OrderedProxy<T> {
fn default() -> Self {
OrderedProxy {
index: 0.0,
body: T::default(),
}
}
}
impl<T: Clone + Default + Sized + Ord> PartialEq for OrderedProxy<T> {
fn eq(&self, other: &OrderedProxy<T>) -> bool {
self.index == other.index && self.body == other.body
}
}
impl<T: Clone + Default + Sized + Ord> Eq for OrderedProxy<T> {}
impl<T: Clone + Default + PartialEq + Ord> PartialOrd for OrderedProxy<T> {
fn partial_cmp(&self, other: &OrderedProxy<T>) -> Option<Ordering> {
if (self.index - other.index).abs() < f64::EPSILON {
self.body.partial_cmp(&other.body)
} else if self.index < other.index {
Some(Ordering::Less)
} else {
Some(Ordering::Greater)
}
}
}
impl<T: Clone + Default + PartialEq + Ord> Ord for OrderedProxy<T> {
fn cmp(&self, other: &OrderedProxy<T>) -> Ordering {
if (self.index - other.index).abs() < f64::EPSILON {
self.body.cmp(&other.body)
} else if self.index < other.index {
Ordering::Less
} else {
Ordering::Greater
}
}
}
impl<T: Clone + Default + Sized + Ord> OrderedProxy<T> {
pub fn new(body: T, index: f64) -> Self {
OrderedProxy { index, body }
}
pub fn new_invert(body: T, rindex: f64) -> Self {
OrderedProxy {
index: -rindex,
body,
}
}
pub fn to(&self) -> T {
self.body.clone()
}
pub fn value(&self) -> f64 {
self.index
}
}
#[cfg(feature = "boundary_check")]
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Propagate {
None,
CacheSatisfied(usize),
FindNewWatch(usize, Lit, Lit),
BecameUnit(usize, Lit),
EmitConflict(usize, Lit),
SandboxCacheSatisfied(usize),
SandboxFindNewWatch(usize, Lit, Lit),
SandboxBecameUnit(usize),
SandboxEmitConflict(usize, Lit),
}
#[cfg(feature = "boundary_check")]
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum VarState {
AssertedSandbox(usize),
Assigned(usize),
AssignedSandbox(usize),
Propagated(usize),
Unassigned(usize),
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_cnf() {
if let Ok(reader) = CNFReader::try_from(Path::new("cnfs/sample.cnf")) {
assert_eq!(reader.cnf.num_of_variables, 250);
assert_eq!(reader.cnf.num_of_clauses, 1065);
} else {
panic!("failed to load cnfs/sample.cnf");
}
}
}