#[cfg(feature = "strategy_adaptation")]
use crate::state::SearchStrategy;
use {
super::{CertifiedRecord, Clause, ClauseDB, ClauseId, WatchDBIF, LBDIF},
crate::{assign::AssignIF, solver::SolverEvent, types::*},
std::{
cmp::Ordering,
ops::{Index, IndexMut, Range, RangeFrom},
slice::{Iter, IterMut},
},
};
const ACTIVITY_MAX: f64 = 1e308;
pub trait ClauseDBIF: IndexMut<ClauseId, Output = Clause> {
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn iter(&self) -> Iter<'_, Clause>;
fn iter_mut(&mut self) -> IterMut<'_, Clause>;
fn bin_watcher_lists(&self) -> &[Vec<Watch>];
fn watcher_list(&self, l: Lit) -> &[Watch];
fn watcher_lists_mut(&mut self) -> &mut [Vec<Watch>];
fn detach(&mut self, cid: ClauseId);
fn check_and_reduce<A>(&mut self, asg: &A, nc: usize) -> bool
where
A: AssignIF;
fn reset(&mut self);
fn garbage_collect(&mut self);
fn registered_bin_clause(&self, l0: Lit, l1: Lit) -> bool;
fn new_clause<A>(
&mut self,
asg: &mut A,
v: &mut Vec<Lit>,
learnt: bool,
level_sort: bool,
) -> ClauseId
where
A: AssignIF;
fn mark_clause_as_used<A>(&mut self, asg: &mut A, cid: ClauseId) -> bool
where
A: AssignIF;
fn count(&self) -> usize;
fn countf(&self, mask: Flag) -> usize;
fn certificate_add(&mut self, vec: &[Lit]);
fn certificate_delete(&mut self, vec: &[Lit]);
fn touch_var(&mut self, vi: VarId);
fn check_size(&self) -> Result<bool, SolverError>;
fn validate(&self, model: &[Option<bool>], strict: bool) -> Option<ClauseId>;
fn strengthen(&mut self, cid: ClauseId, p: Lit) -> bool;
fn minimize_with_biclauses<A>(&mut self, asg: &A, vec: &mut Vec<Lit>)
where
A: AssignIF;
#[cfg(feature = "strategy_adaptation")]
#[cfg(feature = "incremental_solver")]
fn make_permanent_immortal(&mut self, cid: ClauseId);
}
impl Default for ClauseDB {
fn default() -> ClauseDB {
ClauseDB {
clause: Vec::new(),
bin_watcher: Vec::new(),
watcher: Vec::new(),
certified: Vec::new(),
soft_limit: 0,
use_chan_seok: false,
co_lbd_bound: 5,
activity_inc: 1.0,
activity_decay: 0.999,
touched: Vec::new(),
lbd_temp: Vec::new(),
num_lbd_update: 0,
inc_step: 300,
extra_inc: 1000,
first_reduction: 1000,
next_reduction: 1000,
reducible: true,
reduction_coeff: 1,
num_active: 0,
num_bi_clause: 0,
num_bi_learnt: 0,
num_lbd2: 0,
num_learnt: 0,
num_reduction: 0,
during_vivification: false,
eliminated_permanent: Vec::new(),
}
}
}
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<&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 set_activity(&mut self, ci: Self::Ix, val: f64) {
self[ci].reward = val;
}
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 bin_watcher = Vec::with_capacity(2 * (nv + 1));
let mut watcher = Vec::with_capacity(2 * (nv + 1));
let mut touched = Vec::with_capacity(2 * (nv + 1));
for _ in 0..2 * (nv + 1) {
bin_watcher.push(Vec::new());
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,
lbd_temp: vec![0; nv + 1],
bin_watcher,
watcher,
certified,
reducible: config.use_reduce(),
soft_limit: config.c_cls_lim,
..ClauseDB::default()
}
}
fn handle(&mut self, e: SolverEvent) {
match e {
#[cfg(feature = "strategy_adaptation")]
SolverEvent::Adapt(strategy, num_conflict) => {
match strategy {
(_, n) if n != num_conflict => (),
(SearchStrategy::Initial, _) => (),
(SearchStrategy::Generic, _) => (),
(SearchStrategy::LowDecisions, _) => {
self.co_lbd_bound = 4;
self.reduction_coeff =
(num_conflict as f64 / self.next_reduction as f64 + 1.0) as usize;
self.first_reduction = 2000;
self.use_chan_seok = true;
self.inc_step = 0;
self.next_reduction = 2000;
self.make_permanent(true);
}
(SearchStrategy::HighSuccessive, _) => {
self.co_lbd_bound = 3;
self.first_reduction = 30000;
self.use_chan_seok = true;
self.make_permanent(false);
}
(SearchStrategy::LowSuccessive, _) => (),
(SearchStrategy::ManyGlues, _) => (),
}
}
SolverEvent::NewVar => {
self.bin_watcher.push(Vec::new());
self.watcher.push(Vec::new());
self.bin_watcher.push(Vec::new());
self.watcher.push(Vec::new());
self.touched.push(false);
self.touched.push(false);
self.lbd_temp.push(0);
}
SolverEvent::Vivify(on) => {
self.during_vivification = on;
}
_ => (),
}
}
}
impl Export<(usize, usize, usize, usize, usize, usize), bool> for ClauseDB {
#[inline]
fn exports(&self) -> (usize, usize, usize, usize, usize, usize) {
(
self.num_active,
self.num_bi_clause,
self.num_bi_learnt,
self.num_lbd2,
self.num_learnt,
self.num_reduction,
)
}
fn mode(&self) -> bool {
self.use_chan_seok
}
}
impl ClauseDBIF for ClauseDB {
fn len(&self) -> usize {
self.clause.len()
}
fn is_empty(&self) -> bool {
self.clause.is_empty()
}
fn iter(&self) -> Iter<'_, Clause> {
self.clause.iter()
}
fn iter_mut(&mut self) -> IterMut<'_, Clause> {
self.clause.iter_mut()
}
#[inline]
fn watcher_list(&self, l: Lit) -> &[Watch] {
&self.watcher[l]
}
#[inline]
fn bin_watcher_lists(&self) -> &[Vec<Watch>] {
&self.bin_watcher
}
#[inline]
fn watcher_lists_mut(&mut self) -> &mut [Vec<Watch>] {
&mut self.watcher
}
fn garbage_collect(&mut self) {
let ClauseDB {
ref mut bin_watcher,
ref mut watcher,
ref mut clause,
ref mut touched,
ref mut certified,
..
} = self;
debug_assert_eq!(usize::from(!NULL_LIT), 1);
let (recycles, wss) = bin_watcher.split_at_mut(2);
let recycled = &mut recycles[1];
for (i, ws) in &mut wss.iter_mut().enumerate() {
if !touched[i + 2] {
continue;
}
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() && !c.is(Flag::VIV_ASSUMED) {
let temp = c.lits.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
debug_assert!(!temp.is_empty());
certified.push((CertifiedRecord::DELETE, temp));
}
c.lits.clear();
}
ws.detach(n);
}
}
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() && !c.is(Flag::VIV_ASSUMED) {
let temp = c.lits.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
debug_assert!(!temp.is_empty());
certified.push((CertifiedRecord::DELETE, temp));
}
c.lits.clear();
}
ws.detach(n);
}
}
self.num_active = self.clause.len() - recycled.len();
}
fn registered_bin_clause(&self, l0: Lit, l1: Lit) -> bool {
for w in &self.bin_watcher_lists()[usize::from(!l0)] {
if w.blocker == l1 {
return true;
}
}
false
}
fn new_clause<A>(
&mut self,
asg: &mut A,
vec: &mut Vec<Lit>,
mut learnt: bool,
level_sort: bool,
) -> ClauseId
where
A: AssignIF,
{
let reward = self.activity_inc;
let rank = if level_sort {
#[cfg(feature = "boundary_check")]
debug_assert!(1 < vec.len());
if vec.len() <= 2 {
learnt = false;
1
} else {
let lbd = self.compute_lbd(asg, vec);
if lbd == 0 {
vec.len()
} else {
if self.use_chan_seok && lbd <= self.co_lbd_bound {
learnt = false;
}
lbd
}
}
} else {
vec.len()
};
if !self.certified.is_empty() && !self.during_vivification {
let temp = vec.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
debug_assert!(!temp.is_empty());
self.certified.push((CertifiedRecord::ADD, temp));
}
let cid;
let l0 = vec[0];
let l1 = vec[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();
debug_assert!(c.lits.is_empty());
std::mem::swap(&mut c.lits, vec);
c.rank = rank as u16;
c.reward = reward;
c.search_from = 2;
} else {
cid = ClauseId::from(self.clause.len());
let mut c = Clause {
flags: Flag::empty(),
rank: rank as u16,
reward,
..Clause::default()
};
std::mem::swap(&mut c.lits, vec);
self.clause.push(c);
};
if self.during_vivification {
self[cid].turn_on(Flag::VIV_ASSUMED);
}
let c = &mut self[cid];
let len2 = c.lits.len() == 2;
if learnt {
c.turn_on(Flag::LEARNT);
c.turn_on(Flag::JUST_USED);
let lbd2 = c.rank <= 2;
if len2 {
self.num_bi_learnt += 1;
}
if lbd2 {
self.num_lbd2 += 1;
}
self.num_learnt += 1;
}
if len2 {
self.num_bi_clause += 1;
self.bin_watcher[!l0].register(l1, cid);
self.bin_watcher[!l1].register(l0, cid);
} else {
self.watcher[!l0].register(l1, cid);
self.watcher[!l1].register(l0, cid);
}
self.num_active += 1;
cid
}
fn mark_clause_as_used<A>(&mut self, asg: &mut A, cid: ClauseId) -> bool
where
A: AssignIF,
{
let chan_seok_condition = if self.use_chan_seok {
self.co_lbd_bound
} else {
0
};
let nlevels = self.compute_lbd_of(asg, cid);
let c = &mut self[cid];
debug_assert!(!c.is(Flag::DEAD), format!("found {} is dead: {}", cid, c));
if nlevels < c.rank as usize {
match (c.is(Flag::VIVIFIED2), c.is(Flag::VIVIFIED)) {
_ if nlevels == 1 || nlevels + 1 < c.rank as usize => {
c.turn_on(Flag::VIVIFIED2);
c.turn_off(Flag::VIVIFIED);
}
(false, false) => (),
(false, true) => {
c.turn_on(Flag::VIVIFIED2);
c.turn_off(Flag::VIVIFIED);
}
(true, false) => c.turn_on(Flag::VIVIFIED),
(true, true) => (),
}
if !c.is(Flag::JUST_USED) && c.is(Flag::LEARNT) {
c.turn_on(Flag::JUST_USED);
if nlevels < chan_seok_condition {
c.turn_off(Flag::LEARNT);
c.rank = nlevels as u16;
self.num_learnt -= 1;
return true;
}
}
}
c.rank = nlevels as u16;
false
}
fn count(&self) -> usize {
self.clause.len() - self.watcher[!NULL_LIT].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 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 check_and_reduce<A>(&mut self, asg: &A, nc: usize) -> bool
where
A: AssignIF,
{
if !self.reducible || 0 == self.num_learnt {
return false;
}
let go = if self.use_chan_seok {
self.first_reduction < self.num_learnt
} else {
self.reduction_coeff * self.next_reduction <= nc
};
if go {
self.reduction_coeff = ((nc as f64) / (self.next_reduction as f64)) as usize + 1;
self.reduce(asg);
self.num_reduction += 1;
}
go
}
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() && !self.during_vivification {
let temp = vec.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
debug_assert!(!temp.is_empty());
self.certified.push((CertifiedRecord::ADD, temp));
}
}
fn certificate_delete(&mut self, vec: &[Lit]) {
if !self.certified.is_empty() && !self.during_vivification {
let temp = vec.iter().map(|l| i32::from(*l)).collect::<Vec<_>>();
self.certified.push((CertifiedRecord::DELETE, temp));
}
}
fn touch_var(&mut self, vi: VarId) {
self.touched[Lit::from_assign(vi, true)] = true;
self.touched[Lit::from_assign(vi, false)] = true;
}
fn check_size(&self) -> Result<bool, SolverError> {
if self.soft_limit == 0 || self.len() <= self.soft_limit {
Ok(0 == self.soft_limit || 4 * self.count() < 3 * self.soft_limit)
} else {
Err(SolverError::OutOfMemory)
}
}
fn validate(&self, model: &[Option<bool>], strict: bool) -> Option<ClauseId> {
for (i, c) in self.clause.iter().enumerate().skip(1) {
if c.is(Flag::DEAD) || (strict && c.is(Flag::LEARNT)) {
continue;
}
match c.evaluate(model) {
Some(false) => return Some(ClauseId::from(i)),
None if strict => return Some(ClauseId::from(i)),
_ => (),
}
}
None
}
fn strengthen(&mut self, cid: ClauseId, p: Lit) -> bool {
debug_assert!(!self[cid].is(Flag::DEAD));
debug_assert!(1 < self[cid].len());
let c = &mut self[cid];
if (*c).is(Flag::DEAD) {
return false;
}
(*c).turn_on(Flag::JUST_USED);
debug_assert!(1 < usize::from(!p));
(*c).search_from = 2;
let lits = &mut (*c).lits;
debug_assert!(1 < lits.len());
if lits.len() == 2 {
if lits[0] == p {
lits.swap(0, 1);
}
debug_assert!(1 < usize::from(!lits[0]));
return true;
}
if lits[0] == p || lits[1] == p {
let (q, r) = if lits[0] == p {
lits.swap_remove(0);
(lits[0], lits[1])
} else {
lits.swap_remove(1);
(lits[1], lits[0])
};
debug_assert!(1 < usize::from(!p));
if 2 == lits.len() {
self.watcher[!p].detach_with(cid);
self.watcher[!r].detach_with(cid);
self.bin_watcher[!q].register(r, cid);
self.bin_watcher[!r].register(q, cid);
} else {
self.watcher[!p].detach_with(cid);
self.watcher[!q].register(r, cid);
self.watcher[!r].update_blocker(cid, q);
}
} else {
lits.delete_unstable(|&x| x == p);
if 2 == lits.len() {
let q = lits[0];
let r = lits[1];
self.watcher[!q].detach_with(cid);
self.watcher[!r].detach_with(cid);
self.bin_watcher[!q].register(r, cid);
self.bin_watcher[!r].register(q, cid);
}
}
false
}
fn minimize_with_biclauses<A>(&mut self, asg: &A, vec: &mut Vec<Lit>)
where
A: AssignIF,
{
if vec.len() <= 1 {
return;
}
self.lbd_temp[0] += 1;
let key = self.lbd_temp[0];
for l in &vec[1..] {
self.lbd_temp[l.vi() as usize] = key;
}
let l0 = vec[0];
let mut nsat = 0;
for w in &self.bin_watcher[!l0] {
let c = &self.clause[w.c.ordinal as usize];
debug_assert!(c[0] == l0 || c[1] == l0);
let other = c[(c[0] == l0) as usize];
let vi = other.vi();
if self.lbd_temp[vi] == key && asg.assigned(other) == Some(true) {
nsat += 1;
self.lbd_temp[vi] = key - 1;
}
}
if 0 < nsat {
self.lbd_temp[l0.vi()] = key;
vec.retain(|l| self.lbd_temp[l.vi()] == key);
}
}
#[cfg(feature = "incremental_solver")]
fn make_permanent_immortal(&mut self, cid: ClauseId) {
self.eliminated_permanent
.push(self.clause[cid.ordinal as usize].lits.clone());
}
}
impl ClauseDB {
fn reduce<A>(&mut self, asg: &A)
where
A: AssignIF,
{
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, c) in clause.iter_mut().enumerate().skip(1) {
let used = c.is(Flag::JUST_USED);
if c.is(Flag::LEARNT) && !c.is(Flag::DEAD) && !asg.locked(c, ClauseId::from(i)) && !used
{
perm.push(i);
}
if used {
c.turn_off(Flag::JUST_USED)
}
}
if perm.is_empty() {
return;
}
let keep = perm.len() / 2;
if self.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);
}
}
debug_assert!(perm[0..keep].iter().all(|cid| !clause[*cid].is(Flag::DEAD)));
self.garbage_collect();
}
#[cfg(feature = "strategy_adaptation")]
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 as u16 {
c.turn_off(Flag::LEARNT);
self.num_learnt -= 1;
} else if reinit {
c.kill(&mut self.touched);
}
}
self.garbage_collect();
}
}
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;
}
fn evaluate(&self, model: &[Option<bool>]) -> Option<bool> {
let mut falsified = Some(false);
for l in self.lits.iter() {
match model[l.vi()] {
Some(x) if x == bool::from(*l) => return Some(true),
Some(_) => (),
None => falsified = None,
}
}
falsified
}
}