#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::similar_names)]
use crate::sat::assignment::Assignment;
use crate::sat::clause::Clause;
use crate::sat::clause_management::ClauseManagementImpls::LbdActivityClauseManagement;
use crate::sat::clause_storage::LiteralStorage;
use crate::sat::cnf::Cnf;
use crate::sat::literal::Literal;
use crate::sat::propagation::Propagator;
use crate::sat::trail::Trail;
use clap::ValueEnum;
use rustc_hash::{FxHashMap, FxHashSet};
use std::fmt::{Debug, Display};
const DECAY_FACTOR: f64 = 0.95;
pub trait ClauseManagement: Clone + Debug + Default {
fn new<L: Literal, S: LiteralStorage<L>>(clauses: &[Clause<L, S>]) -> Self;
fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, cnf: &mut Cnf<L, S>);
fn should_clean_db(&self) -> bool;
fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
&mut self,
cnf: &mut Cnf<L, S>,
trail: &mut Trail<L, S>,
propagator: &mut P,
assignment: &mut A,
);
fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
&mut self,
cnf: &mut Cnf<L, S>,
c_ref: usize,
);
fn num_removed(&self) -> usize;
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LbdClauseManagement<const N: usize> {
interval: usize,
conflicts_since_last_cleanup: usize,
num_removed: usize,
candidates: Vec<(usize, u32, f64)>,
indices_to_remove: FxHashSet<usize>,
new_learnt_clauses: Vec<Vec<i32>>,
old_to_new_idx_map: FxHashMap<usize, usize>,
}
impl<const N: usize> ClauseManagement for LbdClauseManagement<N> {
fn new<L: Literal, S: LiteralStorage<L>>(clauses: &[Clause<L, S>]) -> Self {
let initial_capacity = clauses.len();
let candidates = Vec::with_capacity(initial_capacity);
let mut indices_to_remove = FxHashSet::default();
indices_to_remove.reserve(initial_capacity);
let new_learnt_clauses = Vec::with_capacity(initial_capacity);
let mut old_to_new_idx_map = FxHashMap::default();
old_to_new_idx_map.reserve(initial_capacity);
Self {
interval: N,
conflicts_since_last_cleanup: 0,
num_removed: 0,
candidates,
indices_to_remove,
new_learnt_clauses,
old_to_new_idx_map,
}
}
fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, cnf: &mut Cnf<L, S>) {
self.conflicts_since_last_cleanup = self.conflicts_since_last_cleanup.wrapping_add(1);
Self::decay_clause_activities(cnf);
}
fn should_clean_db(&self) -> bool {
self.conflicts_since_last_cleanup >= self.interval
}
fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
&mut self,
cnf: &mut Cnf<L, S>,
trail: &mut Trail<L, S>,
propagator: &mut P,
_assignment: &mut A,
) {
let learnt_start_idx = cnf.non_learnt_idx;
if cnf.len() <= learnt_start_idx {
return;
}
self.candidates.clear();
let locked_clauses = trail.get_locked_clauses();
for idx in learnt_start_idx..cnf.len() {
if !locked_clauses.contains(&idx) {
let clause = &cnf[idx];
self.candidates.push((idx, clause.lbd, clause.activity()));
}
}
if self.candidates.is_empty() {
return;
}
let num_candidates = self.candidates.len();
let num_to_remove = num_candidates / 2;
if num_to_remove == 0 {
return;
}
let comparison = |a: &(usize, u32, f64), b: &(usize, u32, f64)| {
let (_, lbd_a, act_a) = a;
let (_, lbd_b, act_b) = b;
let keep_a_heuristic = *lbd_a <= 2;
let keep_b_heuristic = *lbd_b <= 2;
if keep_a_heuristic && !keep_b_heuristic {
return std::cmp::Ordering::Greater;
}
if !keep_a_heuristic && keep_b_heuristic {
return std::cmp::Ordering::Less;
}
match lbd_a.cmp(lbd_b) {
std::cmp::Ordering::Equal => act_a
.partial_cmp(act_b)
.unwrap_or(std::cmp::Ordering::Equal),
other_lbd_cmp => other_lbd_cmp.reverse(),
}
};
self.candidates
.select_nth_unstable_by(num_to_remove, comparison);
self.indices_to_remove.clear();
for (idx, _, _) in self.candidates.iter().take(num_to_remove) {
self.indices_to_remove.insert(*idx);
}
let mut new_learnt_clauses = Vec::with_capacity(num_to_remove);
self.old_to_new_idx_map.clear();
let mut current_new_idx = learnt_start_idx;
for old_idx in learnt_start_idx..cnf.len() {
if !self.indices_to_remove.contains(&old_idx) {
new_learnt_clauses.push(cnf[old_idx].clone());
self.old_to_new_idx_map.insert(old_idx, current_new_idx);
current_new_idx = current_new_idx.wrapping_add(1);
}
}
let new_learnt_count = self.new_learnt_clauses.len();
let new_total_len = learnt_start_idx.wrapping_add(new_learnt_count);
cnf.clauses.truncate(learnt_start_idx);
cnf.clauses.append(&mut new_learnt_clauses);
trail.remap_clause_indices(&self.old_to_new_idx_map);
propagator.cleanup_learnt(learnt_start_idx);
for new_idx in learnt_start_idx..new_total_len {
propagator.add_clause(&cnf[new_idx], new_idx);
}
self.conflicts_since_last_cleanup = 0;
self.num_removed = self.num_removed.wrapping_add(num_to_remove);
}
fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
&mut self,
cnf: &mut Cnf<L, S>,
c_ref: usize,
) {
let clause = &mut cnf[c_ref];
clause.bump_activity(1.0);
}
fn num_removed(&self) -> usize {
self.num_removed
}
}
impl<const N: usize> LbdClauseManagement<N> {
fn decay_clause_activities<L: Literal, S: LiteralStorage<L>>(cnf: &mut Cnf<L, S>) {
for clause in &mut cnf.clauses[cnf.non_learnt_idx..] {
clause.decay_activity(DECAY_FACTOR);
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Copy, Eq)]
pub struct NoClauseManagement;
impl ClauseManagement for NoClauseManagement {
fn new<L: Literal, S: LiteralStorage<L>>(_clauses: &[Clause<L, S>]) -> Self {
Self
}
fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, _cnf: &mut Cnf<L, S>) {}
fn should_clean_db(&self) -> bool {
false
}
fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
&mut self,
_cnf: &mut Cnf<L, S>,
_trail: &mut Trail<L, S>,
_propagator: &mut P,
_assignment: &mut A,
) {
}
fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
&mut self,
_cnf: &mut Cnf<L, S>,
_c_ref: usize,
) {
}
fn num_removed(&self) -> usize {
0
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ClauseManagementImpls<const N: usize> {
NoClauseManagement(NoClauseManagement),
LbdActivityClauseManagement(LbdClauseManagement<N>),
}
impl<const N: usize> Default for ClauseManagementImpls<N> {
fn default() -> Self {
Self::NoClauseManagement(NoClauseManagement)
}
}
impl<const N: usize> ClauseManagement for ClauseManagementImpls<N> {
fn new<L: Literal, S: LiteralStorage<L>>(clauses: &[Clause<L, S>]) -> Self {
LbdActivityClauseManagement(LbdClauseManagement::new(clauses))
}
fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, cnf: &mut Cnf<L, S>) {
match self {
LbdActivityClauseManagement(m) => m.on_conflict(cnf),
Self::NoClauseManagement(m) => m.on_conflict(cnf),
}
}
fn should_clean_db(&self) -> bool {
match self {
LbdActivityClauseManagement(l) => l.should_clean_db(),
Self::NoClauseManagement(m) => m.should_clean_db(),
}
}
fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
&mut self,
cnf: &mut Cnf<L, S>,
trail: &mut Trail<L, S>,
propagator: &mut P,
assignment: &mut A,
) {
match self {
LbdActivityClauseManagement(m) => m.clean_clause_db(cnf, trail, propagator, assignment),
Self::NoClauseManagement(m) => m.clean_clause_db(cnf, trail, propagator, assignment),
}
}
fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
&mut self,
cnf: &mut Cnf<L, S>,
c_ref: usize,
) {
match self {
LbdActivityClauseManagement(m) => m.bump_involved_clause_activities(cnf, c_ref),
Self::NoClauseManagement(m) => m.bump_involved_clause_activities(cnf, c_ref),
}
}
fn num_removed(&self) -> usize {
match self {
LbdActivityClauseManagement(m) => m.num_removed(),
Self::NoClauseManagement(m) => m.num_removed(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, Default, ValueEnum)]
pub enum ClauseManagementType {
NoClauseManagement,
#[default]
LbdActivityClauseManagement,
}
impl Display for ClauseManagementType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoClauseManagement => write!(f, "No Clause Management"),
Self::LbdActivityClauseManagement => write!(f, "LBD and Activity Clause Management"),
}
}
}
impl ClauseManagementType {
#[allow(dead_code)]
pub fn to_impl<L: Literal, S: LiteralStorage<L>, const N: usize>(
self,
clauses: &[Clause<L, S>],
) -> ClauseManagementImpls<N> {
match self {
Self::NoClauseManagement => {
ClauseManagementImpls::NoClauseManagement(NoClauseManagement::new(clauses))
}
Self::LbdActivityClauseManagement => {
LbdActivityClauseManagement(LbdClauseManagement::<N>::new(clauses))
}
}
}
}