#[allow(unused_imports)]
use crate::prelude::*;
use oxiz_sat::{Lit, Var};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PropagationPriority {
Immediate,
High,
Normal,
Low,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropagationType {
Unit,
Theory,
Learned,
Heuristic,
}
#[derive(Debug, Clone)]
pub struct PropagationItem {
pub lit: Lit,
pub reason: Option<Vec<Lit>>,
pub priority: PropagationPriority,
pub prop_type: PropagationType,
pub theory_id: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct PropagationConfig {
pub enable_theory_propagation: bool,
pub enable_learned_propagation: bool,
pub max_queue_size: usize,
pub enable_batching: bool,
}
impl Default for PropagationConfig {
fn default() -> Self {
Self {
enable_theory_propagation: true,
enable_learned_propagation: true,
max_queue_size: 10_000,
enable_batching: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PropagationStats {
pub total_propagations: u64,
pub unit_propagations: u64,
pub theory_propagations: u64,
pub learned_propagations: u64,
pub conflicts: u64,
pub queue_flushes: u64,
}
#[derive(Debug)]
pub struct PropagationPipeline {
config: PropagationConfig,
queue: VecDeque<PropagationItem>,
trail: Vec<Lit>,
assigned: FxHashSet<Var>,
reasons: FxHashMap<Var, Vec<Lit>>,
stats: PropagationStats,
}
impl PropagationPipeline {
pub fn new(config: PropagationConfig) -> Self {
Self {
config,
queue: VecDeque::new(),
trail: Vec::new(),
assigned: FxHashSet::default(),
reasons: FxHashMap::default(),
stats: PropagationStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(PropagationConfig::default())
}
pub fn enqueue(&mut self, item: PropagationItem) {
let var = item.lit.var();
if self.assigned.contains(&var) {
return; }
let pos = self.queue.iter().position(|i| i.priority > item.priority);
if let Some(pos) = pos {
self.queue.insert(pos, item);
} else {
self.queue.push_back(item);
}
if self.queue.len() >= self.config.max_queue_size {
self.stats.queue_flushes += 1;
}
}
pub fn enqueue_unit(&mut self, lit: Lit, reason: Vec<Lit>) {
self.enqueue(PropagationItem {
lit,
reason: Some(reason),
priority: PropagationPriority::Immediate,
prop_type: PropagationType::Unit,
theory_id: None,
});
}
pub fn enqueue_theory(
&mut self,
lit: Lit,
reason: Vec<Lit>,
theory_id: usize,
priority: PropagationPriority,
) {
if !self.config.enable_theory_propagation {
return;
}
self.enqueue(PropagationItem {
lit,
reason: Some(reason),
priority,
prop_type: PropagationType::Theory,
theory_id: Some(theory_id),
});
}
pub fn dequeue(&mut self) -> Option<PropagationItem> {
self.queue.pop_front()
}
pub fn process(&mut self, item: PropagationItem) -> bool {
let var = item.lit.var();
if self.assigned.contains(&var) {
return !self.trail.contains(&item.lit.negate());
}
self.trail.push(item.lit);
self.assigned.insert(var);
if let Some(reason) = item.reason {
self.reasons.insert(var, reason);
}
self.stats.total_propagations += 1;
match item.prop_type {
PropagationType::Unit => self.stats.unit_propagations += 1,
PropagationType::Theory => self.stats.theory_propagations += 1,
PropagationType::Learned => self.stats.learned_propagations += 1,
PropagationType::Heuristic => {}
}
true
}
pub fn propagate_fixpoint(&mut self) -> Option<Vec<Lit>> {
while let Some(item) = self.dequeue() {
if !self.process(item.clone()) {
self.stats.conflicts += 1;
let mut conflict = item.reason.clone().unwrap_or_default();
conflict.push(item.lit.negate());
return Some(conflict);
}
}
None }
pub fn trail(&self) -> &[Lit] {
&self.trail
}
pub fn reason(&self, var: Var) -> Option<&Vec<Lit>> {
self.reasons.get(&var)
}
pub fn is_assigned(&self, var: Var) -> bool {
self.assigned.contains(&var)
}
pub fn backtrack(&mut self, trail_pos: usize) {
while self.trail.len() > trail_pos {
if let Some(lit) = self.trail.pop() {
let var = lit.var();
self.assigned.remove(&var);
self.reasons.remove(&var);
}
}
}
pub fn reset(&mut self) {
self.queue.clear();
self.trail.clear();
self.assigned.clear();
self.reasons.clear();
}
pub fn stats(&self) -> &PropagationStats {
&self.stats
}
pub fn queue_size(&self) -> usize {
self.queue.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lit(n: i32) -> Lit {
Lit::from_dimacs(n)
}
#[test]
fn test_pipeline_creation() {
let pipeline = PropagationPipeline::default_config();
assert_eq!(pipeline.trail().len(), 0);
assert_eq!(pipeline.queue_size(), 0);
}
#[test]
fn test_enqueue_unit() {
let mut pipeline = PropagationPipeline::default_config();
pipeline.enqueue_unit(lit(1), vec![lit(2), lit(3)]);
assert_eq!(pipeline.queue_size(), 1);
}
#[test]
fn test_process_propagation() {
let mut pipeline = PropagationPipeline::default_config();
let item = PropagationItem {
lit: lit(1),
reason: Some(vec![lit(2)]),
priority: PropagationPriority::Immediate,
prop_type: PropagationType::Unit,
theory_id: None,
};
let success = pipeline.process(item);
assert!(success);
assert_eq!(pipeline.trail().len(), 1);
assert!(pipeline.is_assigned(lit(1).var()));
}
#[test]
fn test_conflict_detection() {
let mut pipeline = PropagationPipeline::default_config();
pipeline.process(PropagationItem {
lit: lit(1),
reason: Some(vec![]),
priority: PropagationPriority::Immediate,
prop_type: PropagationType::Unit,
theory_id: None,
});
let success = pipeline.process(PropagationItem {
lit: lit(-1),
reason: Some(vec![lit(2)]),
priority: PropagationPriority::Immediate,
prop_type: PropagationType::Unit,
theory_id: None,
});
assert!(!success); }
#[test]
fn test_backtrack() {
let mut pipeline = PropagationPipeline::default_config();
pipeline.enqueue_unit(lit(1), vec![]);
pipeline.enqueue_unit(lit(2), vec![]);
pipeline.enqueue_unit(lit(3), vec![]);
let _ = pipeline.propagate_fixpoint();
assert_eq!(pipeline.trail().len(), 3);
pipeline.backtrack(1);
assert_eq!(pipeline.trail().len(), 1);
assert!(pipeline.is_assigned(lit(1).var()));
assert!(!pipeline.is_assigned(lit(2).var()));
}
#[test]
fn test_priority_ordering() {
let mut pipeline = PropagationPipeline::default_config();
pipeline.enqueue(PropagationItem {
lit: lit(1),
reason: None,
priority: PropagationPriority::Low,
prop_type: PropagationType::Heuristic,
theory_id: None,
});
pipeline.enqueue(PropagationItem {
lit: lit(2),
reason: None,
priority: PropagationPriority::Immediate,
prop_type: PropagationType::Unit,
theory_id: None,
});
let item = pipeline.dequeue().expect("test operation should succeed");
assert_eq!(item.lit, lit(2));
}
}