use {
crate::{
config::Config,
eliminator::{Eliminator, EliminatorIF},
propagator::{AssignStack, PropagatorIF},
state::{Stat, State, StateIF},
types::*,
var::{VarDB, VarDBIF, LBDIF},
},
std::{
cmp::Ordering,
fmt,
ops::{Index, IndexMut, Range, RangeFrom},
},
};
pub trait ClauseIF {
fn is_empty(&self) -> bool;
fn len(&self) -> usize;
}
pub trait ClauseDBIF {
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn attach(&mut self, state: &mut State, vdb: &mut VarDB, lbd: usize) -> ClauseId;
fn detach(&mut self, cid: ClauseId);
fn reduce(&mut self, state: &mut State, vdb: &mut VarDB);
fn simplify(
&mut self,
asgs: &mut AssignStack,
elim: &mut Eliminator,
state: &mut State,
vdb: &mut VarDB,
) -> MaybeInconsistent;
fn reset(&mut self);
fn garbage_collect(&mut self);
fn new_clause(&mut self, v: &[Lit], rank: usize, learnt: bool) -> ClauseId;
fn count(&self, alive: bool) -> usize;
fn countf(&self, mask: Flag) -> usize;
fn certificate_add(&mut self, vec: &[Lit]);
fn certificate_delete(&mut self, vec: &[Lit]);
fn eliminate_satisfied_clauses(&mut self, elim: &mut Eliminator, vdb: &mut VarDB, occur: bool);
fn check_size(&self) -> MaybeInconsistent;
fn make_permanent(&mut self, reinit: bool);
}
pub trait ClauseIdIF {
fn is_lifted_lit(self) -> bool;
fn format(self) -> String;
}
pub trait WatchDBIF {
fn register(&mut self, blocker: Lit, c: ClauseId);
fn detach(&mut self, n: usize);
fn detach_with(&mut self, cid: ClauseId);
fn update_blocker(&mut self, cid: ClauseId, l: Lit);
}
#[derive(Debug, Eq, PartialEq)]
pub enum CertifiedRecord {
SENTINEL,
ADD,
DELETE,
}
type DRAT = Vec<(CertifiedRecord, Vec<i32>)>;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ClauseId {
pub ordinal: u32,
}
const ACTIVITY_MAX: f64 = 1e308;
const NULL_CLAUSE: ClauseId = ClauseId { ordinal: 0 };
impl Default for ClauseId {
#[inline]
fn default() -> Self {
NULL_CLAUSE
}
}
impl From<usize> for ClauseId {
#[inline]
fn from(u: usize) -> ClauseId {
ClauseId { ordinal: u as u32 }
}
}
impl fmt::Display for ClauseId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CID{}", self.ordinal)
}
}
impl ClauseIdIF for ClauseId {
fn is_lifted_lit(self) -> bool {
0 != 0x8000_0000 & self.ordinal
}
fn format(self) -> String {
if self == ClauseId::default() {
"NullClause".to_string()
} else {
format!("C::{}", self)
}
}
}
#[derive(Clone, Debug)]
pub struct Watch {
pub blocker: Lit,
pub c: ClauseId,
}
impl Default for Watch {
fn default() -> Watch {
Watch {
blocker: NULL_LIT,
c: ClauseId::default(),
}
}
}
impl WatchDBIF for Vec<Watch> {
fn register(&mut self, blocker: Lit, c: ClauseId) {
self.push(Watch { blocker, c });
}
fn detach(&mut self, n: usize) {
self.swap_remove(n);
}
fn detach_with(&mut self, cid: ClauseId) {
for (n, w) in self.iter().enumerate() {
if w.c == cid {
self.swap_remove(n);
return;
}
}
}
fn update_blocker(&mut self, cid: ClauseId, l: Lit) {
for w in &mut self[..] {
if w.c == cid {
w.blocker = l;
return;
}
}
}
}
#[derive(Debug)]
pub struct Clause {
pub lits: Vec<Lit>,
pub rank: usize,
reward: f64,
flags: Flag,
}
impl Default for Clause {
fn default() -> Clause {
Clause {
lits: vec![],
rank: 0,
reward: 0.0,
flags: Flag::empty(),
}
}
}
impl Index<usize> for Clause {
type Output = Lit;
#[inline]
fn index(&self, i: usize) -> &Lit {
unsafe { self.lits.get_unchecked(i) }
}
}
impl IndexMut<usize> for Clause {
#[inline]
fn index_mut(&mut self, i: usize) -> &mut Lit {
unsafe { self.lits.get_unchecked_mut(i) }
}
}
impl Index<Range<usize>> for Clause {
type Output = [Lit];
#[inline]
fn index(&self, r: Range<usize>) -> &[Lit] {
&self.lits[r]
}
}
impl Index<RangeFrom<usize>> for Clause {
type Output = [Lit];
#[inline]
fn index(&self, r: RangeFrom<usize>) -> &[Lit] {
&self.lits[r]
}
}
impl IndexMut<Range<usize>> for Clause {
#[inline]
fn index_mut(&mut self, r: Range<usize>) -> &mut [Lit] {
&mut self.lits[r]
}
}
impl IndexMut<RangeFrom<usize>> for Clause {
#[inline]
fn index_mut(&mut self, r: RangeFrom<usize>) -> &mut [Lit] {
&mut self.lits[r]
}
}
impl ClauseIF for Clause {
fn is_empty(&self) -> bool {
self.lits.is_empty()
}
fn len(&self) -> usize {
self.lits.len()
}
}
impl FlagIF for Clause {
fn is(&self, flag: Flag) -> bool {
self.flags.contains(flag)
}
fn turn_off(&mut self, flag: Flag) {
self.flags.remove(flag);
}
fn turn_on(&mut self, flag: Flag) {
self.flags.insert(flag);
}
}
impl PartialEq for Clause {
fn eq(&self, other: &Clause) -> bool {
self == other
}
}
impl Eq for Clause {}
impl PartialOrd for Clause {
fn partial_cmp(&self, other: &Clause) -> Option<Ordering> {
if self.rank < other.rank {
Some(Ordering::Less)
} else if other.rank < self.rank {
Some(Ordering::Greater)
} else if self.reward > other.reward {
Some(Ordering::Less)
} else if other.reward > self.reward {
Some(Ordering::Greater)
} else {
Some(Ordering::Equal)
}
}
}
impl Ord for Clause {
fn cmp(&self, other: &Clause) -> Ordering {
if self.rank < other.rank {
Ordering::Less
} else if other.rank > self.rank {
Ordering::Greater
} else if self.reward > other.reward {
Ordering::Less
} else if other.reward > self.reward {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
impl Clause {
#[allow(clippy::comparison_chain)]
fn cmp_activity(&self, other: &Clause) -> Ordering {
if self.reward > other.reward {
Ordering::Less
} else if other.reward > self.reward {
Ordering::Greater
} else {
Ordering::Equal
}
}
fn kill(&mut self, touched: &mut [bool]) {
self.turn_on(Flag::DEAD);
debug_assert!(1 < usize::from(self.lits[0]) && 1 < usize::from(self.lits[1]));
touched[!self.lits[0]] = true;
touched[!self.lits[1]] = true;
}
}
impl fmt::Display for Clause {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let st = |flag, mes| if self.is(flag) { mes } else { "" };
write!(
f,
"C{{{:?} {}{}}}",
vec2int(&self.lits),
st(Flag::DEAD, ", dead"),
st(Flag::ENQUEUED, ", enqueued"),
)
}
}
#[derive(Debug)]
pub struct ClauseDB {
clause: Vec<Clause>,
pub touched: Vec<bool>,
pub watcher: Vec<Vec<Watch>>,
pub num_active: usize,
pub num_learnt: usize,
pub certified: DRAT,
pub activity_inc: f64,
pub activity_decay: f64,
pub inc_step: usize,
extra_inc: usize,
pub soft_limit: usize,
pub co_lbd_bound: usize,
pub lbd_frozen_clause: usize,
pub first_reduction: usize,
pub next_reduction: usize,
pub cur_restart: usize,
pub glureduce: bool,
}
impl Default for ClauseDB {
fn default() -> ClauseDB {
ClauseDB {
clause: Vec::new(),
touched: Vec::new(),
watcher: Vec::new(),
num_active: 0,
num_learnt: 0,
certified: Vec::new(),
activity_inc: 1.0,
activity_decay: 0.999,
inc_step: 300,
extra_inc: 1000,
soft_limit: 0,
co_lbd_bound: 5,
lbd_frozen_clause: 30,
first_reduction: 1000,
next_reduction: 1000,
cur_restart: 1,
glureduce: true,
}
}
}
impl Index<ClauseId> for ClauseDB {
type Output = Clause;
#[inline]
fn index(&self, cid: ClauseId) -> &Clause {
unsafe { self.clause.get_unchecked(cid.ordinal as usize) }
}
}
impl IndexMut<ClauseId> for ClauseDB {
#[inline]
fn index_mut(&mut self, cid: ClauseId) -> &mut Clause {
unsafe { self.clause.get_unchecked_mut(cid.ordinal as usize) }
}
}
impl Index<Range<usize>> for ClauseDB {
type Output = [Clause];
#[inline]
fn index(&self, r: Range<usize>) -> &[Clause] {
&self.clause[r]
}
}
impl Index<RangeFrom<usize>> for ClauseDB {
type Output = [Clause];
#[inline]
fn index(&self, r: RangeFrom<usize>) -> &[Clause] {
&self.clause[r]
}
}
impl IndexMut<Range<usize>> for ClauseDB {
#[inline]
fn index_mut(&mut self, r: Range<usize>) -> &mut [Clause] {
&mut self.clause[r]
}
}
impl IndexMut<RangeFrom<usize>> for ClauseDB {
#[inline]
fn index_mut(&mut self, r: RangeFrom<usize>) -> &mut [Clause] {
&mut self.clause[r]
}
}
impl ActivityIF for ClauseDB {
type Ix = ClauseId;
type Inc = ();
fn activity(&mut self, ci: Self::Ix) -> f64 {
self[ci].reward
}
fn bump_activity(&mut self, cid: Self::Ix, _: Self::Inc) {
let c = &mut self.clause[cid.ordinal as usize];
let a = c.reward + self.activity_inc;
c.reward = a;
if ACTIVITY_MAX < a {
let scale = 1.0 / self.activity_inc;
for c in &mut self.clause[1..] {
if c.is(Flag::LEARNT) {
c.reward *= scale;
}
}
self.activity_inc *= scale;
}
}
fn scale_activity(&mut self) {
self.activity_inc /= self.activity_decay;
}
}
impl Instantiate for ClauseDB {
fn instantiate(config: &Config, cnf: &CNFDescription) -> ClauseDB {
let nv = cnf.num_of_variables;
let nc = cnf.num_of_clauses;
let mut clause = Vec::with_capacity(1 + nc);
clause.push(Clause::default());
let mut watcher = Vec::with_capacity(2 * (nv + 1));
let mut touched = Vec::with_capacity(2 * (nv + 1));
for _ in 0..2 * (nv + 1) {
watcher.push(Vec::new());
touched.push(false);
}
let mut certified = Vec::new();
if config.use_certification {
certified.push((CertifiedRecord::SENTINEL, Vec::new()));
}
ClauseDB {
clause,
touched,
watcher,
certified,
soft_limit: config.clause_limit,
..ClauseDB::default()
}
}
}
impl ClauseDBIF for ClauseDB {
fn len(&self) -> usize {
self.clause.len()
}
fn is_empty(&self) -> bool {
self.clause.is_empty()
}
fn garbage_collect(&mut self) {
let ClauseDB {
ref mut watcher,
ref mut clause,
ref mut touched,
ref mut certified,
..
} = self;
debug_assert_eq!(usize::from(!NULL_LIT), 1);
let (recycles, wss) = watcher.split_at_mut(2);
let recycled = &mut recycles[1];
for (i, ws) in &mut wss.iter_mut().enumerate() {
if !touched[i + 2] {
continue;
}
touched[i + 2] = false;
let mut n = 0;
while n < ws.len() {
let cid = ws[n].c;
let c = &mut clause[cid.ordinal as usize];
if !c.is(Flag::DEAD) {
n += 1;
continue;
}
if !c.lits.is_empty() {
debug_assert!(c.is(Flag::DEAD));
recycled.push(Watch {
blocker: NULL_LIT,
c: cid,
});
if c.is(Flag::LEARNT) {
self.num_learnt -= 1;
}
if !certified.is_empty() {
let temp = c.lits.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
certified.push((CertifiedRecord::ADD, temp));
}
c.lits.clear();
}
ws.detach(n);
}
}
self.num_active = self.clause.len() - recycled.len();
}
fn new_clause(&mut self, v: &[Lit], rank: usize, learnt: bool) -> ClauseId {
let cid;
let l0 = v[0];
let l1 = v[1];
if let Some(w) = self.watcher[!NULL_LIT].pop() {
cid = w.c;
let c = &mut self[cid];
debug_assert!(c.is(Flag::DEAD));
c.flags = Flag::empty();
c.lits.clear();
for l in v {
c.lits.push(*l);
}
c.rank = rank;
c.reward = 0.0;
} else {
let mut lits = Vec::with_capacity(v.len());
for l in v {
lits.push(*l);
}
cid = ClauseId::from(self.clause.len());
let c = Clause {
flags: Flag::empty(),
lits,
rank,
reward: 0.0,
};
self.clause.push(c);
};
let c = &mut self[cid];
if learnt {
c.turn_on(Flag::LEARNT);
self.num_learnt += 1;
}
self.watcher[!l0].register(l1, cid);
self.watcher[!l1].register(l0, cid);
self.num_active += 1;
cid
}
fn count(&self, alive: bool) -> usize {
if alive {
self.clause.len() - self.watcher[!NULL_LIT].len() - 1
} else {
self.clause.len() - 1
}
}
fn countf(&self, mask: Flag) -> usize {
self.clause
.iter()
.skip(1)
.filter(|&c| c.flags.contains(mask) && !c.flags.contains(Flag::DEAD))
.count()
}
fn attach(&mut self, state: &mut State, vdb: &mut VarDB, lbd: usize) -> ClauseId {
let v = &mut state.new_learnt;
if !self.certified.is_empty() {
let temp = v.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
self.certified.push((CertifiedRecord::ADD, temp));
}
debug_assert!(1 < v.len());
let mut i_max = 1;
let mut lv_max = 0;
for (i, l) in v.iter().enumerate() {
let vi = l.vi();
let lv = vdb[vi].level;
if vdb[vi].assign.is_some() && lv_max < lv {
i_max = i;
lv_max = lv;
}
}
v.swap(1, i_max);
let learnt = 0 < lbd && 2 < v.len() && (!state.use_chan_seok || self.co_lbd_bound < lbd);
let cid = self.new_clause(&v, lbd, learnt);
let c = &mut self.clause[cid.ordinal as usize];
c.reward = self.activity_inc;
cid
}
fn detach(&mut self, cid: ClauseId) {
let c = &mut self.clause[cid.ordinal as usize];
debug_assert!(!c.is(Flag::DEAD));
debug_assert!(1 < c.lits.len());
c.kill(&mut self.touched);
}
fn reduce(&mut self, state: &mut State, vdb: &mut VarDB) {
vdb.reset_lbd(self);
let ClauseDB {
ref mut clause,
ref mut touched,
..
} = self;
self.next_reduction += self.inc_step;
let mut perm = Vec::with_capacity(clause.len());
for (i, b) in clause.iter().enumerate().skip(1) {
if b.is(Flag::LEARNT) && !b.is(Flag::DEAD) && !vdb.locked(b, ClauseId::from(i)) {
perm.push(i);
}
}
if perm.is_empty() {
return;
}
let keep = perm.len() / 2;
if state.use_chan_seok {
perm.sort_by(|&a, &b| clause[a].cmp_activity(&clause[b]));
} else {
perm.sort_by(|&a, &b| clause[a].cmp(&clause[b]));
if clause[perm[keep]].rank <= 3 {
self.next_reduction += self.extra_inc;
}
if clause[perm[0]].rank <= 5 {
self.next_reduction += self.extra_inc;
};
}
for i in &perm[keep..] {
let c = &mut clause[*i];
if 2 < c.rank {
c.kill(touched);
}
}
state[Stat::Reduction] += 1;
self.garbage_collect();
}
fn simplify(
&mut self,
asgs: &mut AssignStack,
elim: &mut Eliminator,
state: &mut State,
vdb: &mut VarDB,
) -> MaybeInconsistent {
debug_assert_eq!(asgs.level(), 0);
for v in &mut vdb[1..] {
v.reason = ClauseId::default();
}
if elim.is_waiting() {
self.reset();
elim.prepare(self, vdb, true);
}
let start = state.elapsed().unwrap_or(0.0);
loop {
let na = asgs.len();
elim.eliminate(asgs, self, state, vdb)?;
self.eliminate_satisfied_clauses(elim, vdb, true);
if na == asgs.len()
&& (!elim.is_running()
|| (0 == elim.clause_queue_len() && 0 == elim.var_queue_len()))
{
break;
}
if 0.1 <= state.elapsed().unwrap_or(1.0) - start {
elim.clear_clause_queue(self);
elim.clear_var_queue(vdb);
break;
}
}
self.garbage_collect();
state[Stat::SatClauseElimination] += 1;
if elim.is_running() {
state[Stat::ExhaustiveElimination] += 1;
vdb.reset_lbd(self);
elim.stop(self, vdb);
}
if self.check_size().is_err() {
Err(SolverError::Inconsistent)
} else {
Ok(())
}
}
fn reset(&mut self) {
debug_assert!(1 < self.clause.len());
for c in &mut self.clause[1..] {
if c.is(Flag::LEARNT) && !c.is(Flag::DEAD) && self.co_lbd_bound < c.lits.len() {
c.kill(&mut self.touched);
}
}
self.garbage_collect();
}
fn certificate_add(&mut self, vec: &[Lit]) {
if !self.certified.is_empty() {
let temp = vec.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
self.certified.push((CertifiedRecord::ADD, temp));
}
}
fn certificate_delete(&mut self, vec: &[Lit]) {
if !self.certified.is_empty() {
let temp = vec.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
self.certified.push((CertifiedRecord::DELETE, temp));
}
}
fn eliminate_satisfied_clauses(
&mut self,
elim: &mut Eliminator,
vdb: &mut VarDB,
update_occur: bool,
) {
for (cid, c) in &mut self.clause.iter_mut().enumerate().skip(1) {
if !c.is(Flag::DEAD) && vdb.satisfies(&c.lits) {
c.kill(&mut self.touched);
if elim.is_running() {
if update_occur {
elim.remove_cid_occur(vdb, ClauseId::from(cid), c);
}
for l in &c.lits {
elim.enqueue_var(vdb, l.vi(), true);
}
}
}
}
}
fn check_size(&self) -> MaybeInconsistent {
if self.soft_limit == 0 || self.count(false) <= self.soft_limit {
Ok(())
} else {
Err(SolverError::Inconsistent)
}
}
fn make_permanent(&mut self, reinit: bool) {
for c in &mut self.clause[1..] {
if c.is(Flag::DEAD) || !c.is(Flag::LEARNT) {
continue;
}
if c.rank <= self.co_lbd_bound {
c.turn_off(Flag::LEARNT);
self.num_learnt -= 1;
} else if reinit {
c.kill(&mut self.touched);
}
}
self.garbage_collect();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lit(i: i32) -> Lit {
Lit::from(i)
}
#[test]
fn test_clause_equality() -> () {
let config = Config::default();
let cnf = CNFDescription {
num_of_variables: 4,
..CNFDescription::default()
};
let mut cdb = ClauseDB::instantiate(&config, &cnf);
let c1 = cdb.new_clause(&[lit(1), lit(2), lit(3)], 0, false);
let c2 = cdb.new_clause(&[lit(-1), lit(4)], 0, false);
cdb[c2].reward = 2.4;
assert_eq!(c1, c1);
assert_eq!(c1 == c1, true);
assert_ne!(c1, c2);
assert_eq!(cdb.activity(c2), 2.4);
}
#[test]
fn test_clause_iterator() -> () {
let config = Config::default();
let cnf = CNFDescription {
num_of_variables: 4,
..CNFDescription::default()
};
let mut cdb = ClauseDB::instantiate(&config, &cnf);
let c1 = cdb.new_clause(&[lit(1), lit(2), lit(3)], 0, false);
assert_eq!(cdb[c1][0..].iter().map(|l| i32::from(*l)).sum::<i32>(), 6);
let mut iter = cdb[c1][0..].into_iter();
assert_eq!(iter.next(), Some(&lit(1)));
assert_eq!(iter.next(), Some(&lit(2)));
assert_eq!(iter.next(), Some(&lit(3)));
assert_eq!(iter.next(), None);
}
}