#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use oxiz_sat::Lit;
pub type Priority = u8;
pub type PropagationId = usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropagationType {
Boolean,
Equality,
Theory(u8),
Heuristic,
}
impl PropagationType {
pub fn priority(&self) -> Priority {
match self {
PropagationType::Boolean => 0,
PropagationType::Equality => 1,
PropagationType::Theory(theory_id) => 2 + theory_id,
PropagationType::Heuristic => 255,
}
}
}
#[derive(Debug, Clone)]
pub struct Propagation {
pub id: PropagationId,
pub prop_type: PropagationType,
pub priority: Priority,
pub literal: Option<Lit>,
pub timestamp: u64,
}
impl PartialEq for Propagation {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Propagation {}
impl PartialOrd for Propagation {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Propagation {
fn cmp(&self, other: &Self) -> Ordering {
other
.priority
.cmp(&self.priority)
.then_with(|| other.timestamp.cmp(&self.timestamp))
}
}
#[derive(Debug, Clone)]
pub struct PriorityQueueConfig {
pub use_timestamps: bool,
pub max_size: usize,
}
impl Default for PriorityQueueConfig {
fn default() -> Self {
Self {
use_timestamps: true,
max_size: 100_000,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PriorityQueueStats {
pub enqueued: u64,
pub dequeued: u64,
pub peak_size: usize,
pub by_type: FxHashMap<u8, u64>,
}
#[derive(Debug)]
pub struct PropagationQueue {
heap: BinaryHeap<Propagation>,
next_id: PropagationId,
timestamp: u64,
config: PriorityQueueConfig,
stats: PriorityQueueStats,
}
impl PropagationQueue {
pub fn new(config: PriorityQueueConfig) -> Self {
Self {
heap: BinaryHeap::new(),
next_id: 0,
timestamp: 0,
config,
stats: PriorityQueueStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(PriorityQueueConfig::default())
}
pub fn enqueue(&mut self, prop_type: PropagationType, literal: Option<Lit>) -> PropagationId {
let id = self.next_id;
self.next_id += 1;
let priority = prop_type.priority();
let timestamp = if self.config.use_timestamps {
let ts = self.timestamp;
self.timestamp += 1;
ts
} else {
0
};
let prop = Propagation {
id,
prop_type,
priority,
literal,
timestamp,
};
self.heap.push(prop);
self.stats.enqueued += 1;
if self.heap.len() > self.stats.peak_size {
self.stats.peak_size = self.heap.len();
}
*self.stats.by_type.entry(priority).or_insert(0) += 1;
id
}
pub fn dequeue(&mut self) -> Option<Propagation> {
let prop = self.heap.pop()?;
self.stats.dequeued += 1;
Some(prop)
}
pub fn peek(&self) -> Option<&Propagation> {
self.heap.peek()
}
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
pub fn len(&self) -> usize {
self.heap.len()
}
pub fn clear(&mut self) {
self.heap.clear();
self.timestamp = 0;
}
pub fn stats(&self) -> &PriorityQueueStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = PriorityQueueStats::default();
}
}
impl Default for PropagationQueue {
fn default() -> Self {
Self::default_config()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_queue_creation() {
let queue = PropagationQueue::default_config();
assert!(queue.is_empty());
assert_eq!(queue.len(), 0);
}
#[test]
fn test_priority_ordering() {
use oxiz_sat::Var;
let mut queue = PropagationQueue::default_config();
let _heuristic = queue.enqueue(PropagationType::Heuristic, None);
let _theory = queue.enqueue(PropagationType::Theory(2), None);
let _equality = queue.enqueue(PropagationType::Equality, None);
let boolean = queue.enqueue(PropagationType::Boolean, Some(Lit::pos(Var::new(0))));
let first = queue.dequeue().expect("test operation should succeed");
assert_eq!(first.id, boolean);
assert_eq!(first.priority, 0);
}
#[test]
fn test_timestamp_fifo() {
let mut queue = PropagationQueue::default_config();
let id1 = queue.enqueue(PropagationType::Theory(0), None);
let id2 = queue.enqueue(PropagationType::Theory(0), None);
let id3 = queue.enqueue(PropagationType::Theory(0), None);
assert_eq!(
queue.dequeue().expect("test operation should succeed").id,
id1
);
assert_eq!(
queue.dequeue().expect("test operation should succeed").id,
id2
);
assert_eq!(
queue.dequeue().expect("test operation should succeed").id,
id3
);
}
#[test]
fn test_peek() {
use oxiz_sat::Var;
let mut queue = PropagationQueue::default_config();
let id = queue.enqueue(PropagationType::Boolean, Some(Lit::pos(Var::new(0))));
let peeked = queue.peek().expect("test operation should succeed");
assert_eq!(peeked.id, id);
assert_eq!(queue.len(), 1);
let dequeued = queue.dequeue().expect("test operation should succeed");
assert_eq!(dequeued.id, id);
assert_eq!(queue.len(), 0);
}
#[test]
fn test_clear() {
use oxiz_sat::Var;
let mut queue = PropagationQueue::default_config();
queue.enqueue(PropagationType::Boolean, Some(Lit::pos(Var::new(0))));
queue.enqueue(PropagationType::Equality, None);
assert_eq!(queue.len(), 2);
queue.clear();
assert!(queue.is_empty());
}
#[test]
fn test_stats() {
use oxiz_sat::Var;
let mut queue = PropagationQueue::default_config();
queue.enqueue(PropagationType::Boolean, Some(Lit::pos(Var::new(0))));
queue.enqueue(PropagationType::Boolean, Some(Lit::pos(Var::new(1))));
queue.dequeue();
let stats = queue.stats();
assert_eq!(stats.enqueued, 2);
assert_eq!(stats.dequeued, 1);
assert_eq!(stats.peak_size, 2);
}
}