#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
use crate::sat::assignment::Assignment;
use crate::sat::clause::Clause;
use crate::sat::clause_storage::LiteralStorage;
use crate::sat::cnf::Cnf;
use crate::sat::literal::{Literal, Variable};
use crate::sat::trail::{Reason, Trail};
use bit_vec::BitVec;
use smallvec::SmallVec;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum Conflict<T: Literal, S: LiteralStorage<T>> {
#[default]
Ground,
Unit(Clause<T, S>),
Learned(usize, Clause<T, S>),
Restart(Clause<T, S>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct Analyser<T: Literal, S: LiteralStorage<T>, const N: usize = 12> {
seen: BitVec,
path_c: usize,
to_bump: SmallVec<[T; N]>,
data: std::marker::PhantomData<*const (T, S)>,
pub count: usize,
}
impl<T: Literal, S: LiteralStorage<T>, const N: usize> Analyser<T, S, N> {
pub(crate) fn new(num_vars: usize) -> Self {
Self {
seen: BitVec::from_elem(num_vars, false),
path_c: 0,
to_bump: SmallVec::with_capacity(N),
data: std::marker::PhantomData,
count: 0,
}
}
#[inline]
fn is_seen(&self, var_id: Variable) -> bool {
unsafe { self.seen.get_unchecked(var_id as usize) }
}
#[inline]
fn set_seen(&mut self, var_id: Variable) {
unsafe {
*self.seen.get_unchecked_mut(var_id as usize) = true;
}
}
#[inline]
fn unset_seen(&mut self, var_id: Variable) {
unsafe {
*self.seen.get_unchecked_mut(var_id as usize) = false;
}
}
fn resolve(
&mut self,
c: &mut Clause<T, S>,
o: &Clause<T, S>,
assignment: &impl Assignment,
pivot_var: Variable,
c_idx_of_pivot_lit: usize,
trail: &Trail<T, S>,
) {
c.remove_literal(c_idx_of_pivot_lit);
self.path_c = self.path_c.saturating_sub(1);
self.unset_seen(pivot_var);
for &lit_from_o in o.iter() {
let var_from_o = lit_from_o.variable();
if !self.is_seen(var_from_o) && assignment.literal_value(lit_from_o) == Some(false) {
self.set_seen(var_from_o);
self.to_bump.push(lit_from_o);
c.push(lit_from_o);
if trail.level(var_from_o) >= trail.decision_level() {
self.path_c = self.path_c.wrapping_add(1);
}
}
}
}
fn choose_literal(
&self,
c: &Clause<T, S>,
trail: &Trail<T, S>,
i: &mut usize,
) -> Option<usize> {
while *i > 0 {
*i -= 1;
let current_trail_entry = &trail[*i];
let var_on_trail = current_trail_entry.lit.variable();
if self.is_seen(var_on_trail) {
for k in 0..c.len() {
if var_on_trail == c[k].variable() {
return Some(k);
}
}
}
}
None
}
pub(crate) fn analyse_conflict(
&mut self,
cnf: &Cnf<T, S>,
trail: &Trail<T, S>,
assignment: &impl Assignment,
conflicting_clause_ref: usize,
) -> (Conflict<T, S>, SmallVec<[T; N]>) {
self.count = self.count.wrapping_add(1);
self.reset_for_analysis(cnf.num_vars);
let current_decision_level = trail.decision_level();
let mut resolvent_clause = cnf[conflicting_clause_ref].clone();
self.path_c = 0;
for &lit in resolvent_clause.iter() {
let var = lit.variable();
self.set_seen(var);
self.to_bump.push(lit);
if trail.level(var) >= current_decision_level {
self.path_c = self.path_c.wrapping_add(1);
}
}
let mut trail_idx = trail.len();
while self.path_c > usize::from(current_decision_level != 0) {
let Some(idx_in_resolvent_of_pivot) =
self.choose_literal(&resolvent_clause, trail, &mut trail_idx)
else {
break;
};
let antecedent_reason = trail[trail_idx].reason;
let antecedent_clause = match antecedent_reason {
Reason::Clause(ante_idx) | Reason::Unit(ante_idx) => cnf[ante_idx].clone(),
Reason::Decision => break,
};
let pivot_var = trail[trail_idx].lit.variable();
self.resolve(
&mut resolvent_clause,
&antecedent_clause,
assignment,
pivot_var,
idx_in_resolvent_of_pivot,
trail,
);
}
debug_assert!(
resolvent_clause
.iter()
.all(|&lit| assignment.literal_value(lit) == Some(false)),
"Learnt clause not entirely false under current assignment!"
);
let literals_to_bump = self.to_bump.clone();
if resolvent_clause.is_empty() {
(Conflict::Ground, literals_to_bump)
} else if resolvent_clause.is_unit() {
(Conflict::Unit(resolvent_clause), literals_to_bump)
} else {
if current_decision_level > 0 && self.path_c != 1 {
return (Conflict::Restart(resolvent_clause), literals_to_bump);
}
let mut asserting_lit_idx_in_clause: usize = 0;
let mut max_trail_pos_at_dl = 0;
for k in 0..resolvent_clause.len() {
let var = resolvent_clause[k].variable();
if trail.level(var) == current_decision_level {
let pos_on_trail = trail.lit_to_pos[var as usize];
if pos_on_trail > max_trail_pos_at_dl {
max_trail_pos_at_dl = pos_on_trail;
asserting_lit_idx_in_clause = k;
}
}
}
(
Conflict::Learned(asserting_lit_idx_in_clause, resolvent_clause),
literals_to_bump,
)
}
}
fn reset_for_analysis(&mut self, num_vars: usize) {
if self.seen.len() == num_vars {
self.seen.clear();
} else {
self.seen = BitVec::from_elem(num_vars, false);
}
self.path_c = 0;
self.to_bump.clear();
}
}
#[cfg(test)]
mod tests {
}