#[allow(unused_imports)]
use crate::prelude::*;
use crate::clause::{Clause, ClauseTier};
use crate::literal::Lit;
use smallvec::SmallVec;
const NUM_BUCKETS: usize = 5;
fn bucket_for_size(num_lits: usize) -> usize {
match num_lits {
0..=2 => 0,
3 => 1,
4..=7 => 2,
8..=15 => 3,
_ => 4,
}
}
#[derive(Debug, Clone, Default)]
pub struct ClausePoolStats {
pub total_returned: usize,
pub total_reused: usize,
pub total_fresh: usize,
pub pool_size: usize,
pub bucket_sizes: [usize; NUM_BUCKETS],
}
impl ClausePoolStats {
#[must_use]
pub fn reuse_ratio(&self) -> f64 {
let total = self.total_reused + self.total_fresh;
if total == 0 {
return 0.0;
}
self.total_reused as f64 / total as f64
}
}
#[derive(Debug)]
pub struct ClausePool {
buckets: [Vec<Clause>; NUM_BUCKETS],
max_per_bucket: usize,
stats: ClausePoolStats,
}
impl Default for ClausePool {
fn default() -> Self {
Self::new()
}
}
impl ClausePool {
#[must_use]
pub fn new() -> Self {
Self::with_max_per_bucket(512)
}
#[must_use]
pub fn with_max_per_bucket(max_per_bucket: usize) -> Self {
Self {
buckets: [Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()],
max_per_bucket,
stats: ClausePoolStats::default(),
}
}
pub fn recycle(&mut self, mut clause: Clause) {
let bucket = bucket_for_size(clause.lits.len());
if self.buckets[bucket].len() >= self.max_per_bucket {
return;
}
clause.lits.clear();
clause.learned = false;
clause.activity = 0.0;
clause.lbd = 0;
clause.deleted = false;
clause.tier = ClauseTier::Local;
clause.usage_count = 0;
self.buckets[bucket].push(clause);
self.stats.total_returned += 1;
self.stats.pool_size += 1;
self.stats.bucket_sizes[bucket] += 1;
}
pub fn acquire(&mut self, lits: &[Lit], learned: bool) -> Option<Clause> {
let bucket = bucket_for_size(lits.len());
if let Some(mut clause) = self.buckets[bucket].pop() {
clause.lits.extend_from_slice(lits);
clause.learned = learned;
clause.activity = 0.0;
clause.lbd = 0;
clause.deleted = false;
clause.tier = ClauseTier::Local;
clause.usage_count = 0;
self.stats.total_reused += 1;
self.stats.pool_size = self.stats.pool_size.saturating_sub(1);
self.stats.bucket_sizes[bucket] = self.stats.bucket_sizes[bucket].saturating_sub(1);
Some(clause)
} else {
self.stats.total_fresh += 1;
None
}
}
pub fn make_clause(&mut self, lits: impl IntoIterator<Item = Lit>, learned: bool) -> Clause {
let collected: SmallVec<[Lit; 4]> = lits.into_iter().collect();
if let Some(clause) = self.acquire(&collected, learned) {
clause
} else {
Clause::new(collected, learned)
}
}
#[must_use]
pub fn stats(&self) -> &ClausePoolStats {
&self.stats
}
#[must_use]
pub fn pool_size(&self) -> usize {
self.stats.pool_size
}
pub fn shrink_to(&mut self, max: usize) {
let mut total = self.pool_size();
if total <= max {
return;
}
for bucket in (0..NUM_BUCKETS).rev() {
while total > max {
if self.buckets[bucket].pop().is_some() {
total -= 1;
self.stats.pool_size -= 1;
self.stats.bucket_sizes[bucket] =
self.stats.bucket_sizes[bucket].saturating_sub(1);
} else {
break;
}
}
}
}
pub fn clear(&mut self) {
for bucket in &mut self.buckets {
bucket.clear();
}
self.stats.pool_size = 0;
self.stats.bucket_sizes = [0; NUM_BUCKETS];
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::literal::Var;
fn lits(vars: &[u32], signs: &[bool]) -> Vec<Lit> {
vars.iter()
.zip(signs.iter())
.map(|(&v, &s)| {
if s {
Lit::pos(Var::new(v))
} else {
Lit::neg(Var::new(v))
}
})
.collect()
}
#[test]
fn test_pool_creation() {
let pool = ClausePool::new();
assert_eq!(pool.pool_size(), 0);
assert_eq!(pool.stats().total_returned, 0);
assert_eq!(pool.stats().total_reused, 0);
}
#[test]
fn test_recycle_and_acquire() {
let mut pool = ClausePool::new();
let clause = Clause::learned(lits(&[0, 1, 2], &[true, false, true]));
assert_eq!(clause.len(), 3);
pool.recycle(clause);
assert_eq!(pool.pool_size(), 1);
assert_eq!(pool.stats().total_returned, 1);
let new_lits = lits(&[3, 4, 5], &[false, true, false]);
let reused = pool.acquire(&new_lits, true);
assert!(reused.is_some());
let reused = reused.expect("should have a reused clause");
assert_eq!(reused.len(), 3);
assert!(reused.learned);
assert_eq!(pool.stats().total_reused, 1);
}
#[test]
fn test_acquire_empty_pool() {
let mut pool = ClausePool::new();
let new_lits = lits(&[0, 1], &[true, false]);
let result = pool.acquire(&new_lits, false);
assert!(result.is_none());
assert_eq!(pool.stats().total_fresh, 1);
}
#[test]
fn test_make_clause_reuses() {
let mut pool = ClausePool::new();
let clause = Clause::original(lits(&[0, 1], &[true, true]));
pool.recycle(clause);
let made = pool.make_clause(lits(&[2, 3], &[false, false]), false);
assert_eq!(made.len(), 2);
assert!(!made.learned);
assert_eq!(pool.stats().total_reused, 1);
}
#[test]
fn test_make_clause_fresh() {
let mut pool = ClausePool::new();
let made = pool.make_clause(lits(&[0, 1, 2, 3], &[true, true, false, false]), true);
assert_eq!(made.len(), 4);
assert!(made.learned);
assert_eq!(pool.stats().total_fresh, 1);
assert_eq!(pool.stats().total_reused, 0);
}
#[test]
fn test_pool_stats_reuse_ratio() {
let mut pool = ClausePool::new();
let _ = pool.make_clause(lits(&[0, 1], &[true, false]), false);
let c = Clause::original(lits(&[2, 3], &[true, true]));
pool.recycle(c);
let _ = pool.make_clause(lits(&[4, 5], &[false, false]), false);
let stats = pool.stats();
assert_eq!(stats.total_fresh, 1);
assert_eq!(stats.total_reused, 1);
let ratio = stats.reuse_ratio();
assert!((ratio - 0.5).abs() < 1e-10);
}
#[test]
fn test_shrink_to() {
let mut pool = ClausePool::new();
for i in 0..10u32 {
let c = Clause::original(lits(&[i, i + 10], &[true, false]));
pool.recycle(c);
}
assert_eq!(pool.pool_size(), 10);
pool.shrink_to(5);
assert!(pool.pool_size() <= 5);
}
#[test]
fn test_clear_pool() {
let mut pool = ClausePool::new();
for i in 0..5u32 {
let c = Clause::learned(lits(&[i, i + 1, i + 2], &[true, false, true]));
pool.recycle(c);
}
assert_eq!(pool.pool_size(), 5);
pool.clear();
assert_eq!(pool.pool_size(), 0);
}
#[test]
fn test_bucket_overflow() {
let mut pool = ClausePool::with_max_per_bucket(2);
for i in 0..3u32 {
let c = Clause::original(lits(&[i, i + 10], &[true, false]));
pool.recycle(c);
}
assert!(pool.pool_size() <= 2);
}
#[test]
fn test_different_size_buckets() {
let mut pool = ClausePool::new();
pool.recycle(Clause::original(lits(&[0, 1], &[true, false])));
pool.recycle(Clause::original(lits(&[0, 1, 2], &[true, false, true])));
assert_eq!(pool.pool_size(), 2);
let acquired = pool.acquire(&lits(&[3, 4, 5], &[false, true, false]), false);
assert!(acquired.is_some());
assert_eq!(pool.pool_size(), 1);
let acquired2 = pool.acquire(&lits(&[6, 7], &[true, true]), false);
assert!(acquired2.is_some());
assert_eq!(pool.pool_size(), 0);
}
}