pumpkin_solver/engine/sat/
learned_clause_manager.rsuse super::AssignmentsPropositional;
use crate::basic_types::ClauseReference;
use crate::engine::clause_allocators::ClauseAllocatorInterface;
use crate::engine::clause_allocators::ClauseInterface;
use crate::engine::constraint_satisfaction_solver::ClausalPropagatorType;
use crate::engine::constraint_satisfaction_solver::ClauseAllocator;
use crate::engine::variables::Literal;
use crate::propagators::clausal::is_clause_propagating;
use crate::propagators::clausal::ClausalPropagator;
use crate::pumpkin_assert_moderate;
#[cfg(doc)]
use crate::Solver;
#[derive(Debug, Copy, Clone)]
pub struct LearningOptions {
pub max_clause_activity: f32,
pub clause_activity_decay_factor: f32,
pub num_high_lbd_learned_clauses_max: u64,
pub high_lbd_learned_clause_sorting_strategy: LearnedClauseSortingStrategy,
pub lbd_threshold: u32,
}
impl Default for LearningOptions {
fn default() -> Self {
Self {
max_clause_activity: 1e20,
clause_activity_decay_factor: 0.99,
num_high_lbd_learned_clauses_max: 4000,
high_lbd_learned_clause_sorting_strategy: LearnedClauseSortingStrategy::Activity,
lbd_threshold: 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LearnedClauseSortingStrategy {
Activity,
Lbd,
}
impl std::fmt::Display for LearnedClauseSortingStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
LearnedClauseSortingStrategy::Lbd => write!(f, "lbd"),
LearnedClauseSortingStrategy::Activity => write!(f, "activity"),
}
}
}
#[derive(Default, Debug)]
struct LearnedClauses {
low_lbd: Vec<ClauseReference>,
high_lbd: Vec<ClauseReference>,
}
#[derive(Debug)]
pub(crate) struct LearnedClauseManager {
learned_clauses: LearnedClauses,
parameters: LearningOptions,
clause_bump_increment: f32,
}
impl LearnedClauseManager {
pub(crate) fn new(sat_options: LearningOptions) -> Self {
LearnedClauseManager {
learned_clauses: LearnedClauses::default(),
parameters: sat_options,
clause_bump_increment: 1.0,
}
}
pub(crate) fn add_learned_clause(
&mut self,
learned_clause_literals: Vec<Literal>,
clausal_propagator: &mut ClausalPropagatorType,
assignments: &mut AssignmentsPropositional,
clause_allocator: &mut ClauseAllocator,
) -> ClauseReference {
let result = clausal_propagator.add_asserting_learned_clause(
learned_clause_literals,
assignments,
clause_allocator,
);
if let Some(clause_reference) = result {
self.update_lbd(clause_reference, assignments, clause_allocator);
if clause_allocator[clause_reference].lbd() <= self.parameters.lbd_threshold {
self.learned_clauses.low_lbd.push(clause_reference);
} else {
self.learned_clauses.high_lbd.push(clause_reference);
}
return clause_reference;
}
unreachable!("This should always allocate a clause");
}
pub(crate) fn shrink_learned_clause_database_if_needed(
&mut self,
assignments: &AssignmentsPropositional,
clause_allocator: &mut ClauseAllocator,
clausal_propagator: &mut ClausalPropagatorType,
) {
if self.learned_clauses.high_lbd.len()
<= self.parameters.num_high_lbd_learned_clauses_max as usize
{
return;
}
self.promote_high_lbd_clauses(clause_allocator);
self.remove_high_lbd_clauses(assignments, clause_allocator, clausal_propagator);
}
fn remove_high_lbd_clauses(
&mut self,
assignments: &AssignmentsPropositional,
clause_allocator: &mut ClauseAllocator,
clausal_propagator: &mut ClausalPropagatorType,
) {
self.sort_high_lbd_clauses_by_quality_decreasing_order(clause_allocator);
let mut num_clauses_to_remove = self.learned_clauses.high_lbd.len() as u64
- self.parameters.num_high_lbd_learned_clauses_max / 2;
for &clause_reference in self.learned_clauses.high_lbd.iter().rev() {
if num_clauses_to_remove == 0 {
break;
}
if clause_allocator[clause_reference].is_protected_against_deletion() {
clause_allocator[clause_reference].clear_protection_against_deletion();
continue;
}
if is_clause_propagating(assignments, clause_allocator, clause_reference) {
continue;
}
clausal_propagator.remove_clause_from_consideration(
clause_allocator[clause_reference].get_literal_slice(),
clause_reference,
);
clause_allocator.delete_clause(clause_reference);
num_clauses_to_remove -= 1;
}
self.learned_clauses
.high_lbd
.retain(|&clause_reference| !clause_allocator[clause_reference].is_deleted());
}
fn sort_high_lbd_clauses_by_quality_decreasing_order(
&mut self,
clause_allocator: &mut ClauseAllocator,
) {
self.learned_clauses
.high_lbd
.sort_unstable_by(|clause_reference1, clause_reference2| {
let clause1 = clause_allocator.get_clause(*clause_reference1);
let clause2 = clause_allocator.get_clause(*clause_reference2);
match self.parameters.high_lbd_learned_clause_sorting_strategy {
LearnedClauseSortingStrategy::Activity => {
clause2
.get_activity()
.partial_cmp(&clause1.get_activity())
.unwrap()
}
LearnedClauseSortingStrategy::Lbd => {
if clause1.lbd() != clause2.lbd() {
clause1.lbd().cmp(&clause2.lbd())
} else {
clause2
.get_activity()
.partial_cmp(&clause1.get_activity())
.unwrap()
}
}
}
});
}
fn promote_high_lbd_clauses(&mut self, clause_allocator: &mut ClauseAllocator) {
for &clause_reference in &self.learned_clauses.high_lbd {
let lbd = clause_allocator[clause_reference].lbd();
if lbd <= self.parameters.lbd_threshold {
self.learned_clauses.low_lbd.push(clause_reference);
}
}
self.learned_clauses.high_lbd.retain(|&clause_reference| {
clause_allocator[clause_reference].lbd() > self.parameters.lbd_threshold
});
}
pub(crate) fn update_clause_lbd_and_bump_activity(
&mut self,
clause_reference: ClauseReference,
assignments: &AssignmentsPropositional,
clause_allocator: &mut ClauseAllocator,
) {
if clause_allocator.get_clause(clause_reference).is_learned()
&& clause_allocator.get_clause(clause_reference).lbd() > self.parameters.lbd_threshold
{
self.bump_clause_activity(clause_reference, clause_allocator);
self.update_lbd(clause_reference, assignments, clause_allocator);
}
}
pub(crate) fn update_lbd(
&mut self,
clause_reference: ClauseReference,
assignments: &AssignmentsPropositional,
clause_allocator: &mut ClauseAllocator,
) {
let new_lbd = self.compute_lbd_for_literals(
clause_allocator[clause_reference].get_literal_slice(),
assignments,
);
if new_lbd < clause_allocator[clause_reference].lbd() {
clause_allocator[clause_reference].update_lbd(new_lbd);
if new_lbd <= 30 {
clause_allocator[clause_reference].mark_protection_against_deletion();
}
}
}
pub(crate) fn compute_lbd_for_literals(
&self,
literals: &[Literal],
assignments: &AssignmentsPropositional,
) -> u32 {
pumpkin_assert_moderate!(
literals
.iter()
.all(|lit| assignments.is_literal_assigned(*lit)),
"Cannot compute LBD if not all literals are assigned."
);
let mut codes: Vec<usize> = literals
.iter()
.filter_map(|lit| {
let level = assignments.get_literal_assignment_level(*lit);
if level > 0 {
Some(level)
} else {
None
}
})
.collect();
codes.sort_unstable();
codes.dedup();
codes.len() as u32
}
pub(crate) fn bump_clause_activity(
&mut self,
clause_reference: ClauseReference,
clause_allocator: &mut ClauseAllocator,
) {
if clause_allocator.get_clause(clause_reference).get_activity() + self.clause_bump_increment
> self.parameters.max_clause_activity
{
self.rescale_clause_activities(clause_allocator);
}
clause_allocator
.get_mutable_clause(clause_reference)
.increase_activity(self.clause_bump_increment);
}
pub(crate) fn rescale_clause_activities(&mut self, clause_allocator: &mut ClauseAllocator) {
self.learned_clauses
.high_lbd
.iter()
.for_each(|clause_reference| {
let clause = clause_allocator.get_mutable_clause(*clause_reference);
clause.divide_activity(self.parameters.max_clause_activity);
});
self.clause_bump_increment /= self.parameters.max_clause_activity;
}
pub(crate) fn decay_clause_activities(&mut self) {
self.clause_bump_increment /= self.parameters.clause_activity_decay_factor;
}
}