#[allow(unused_imports)]
use crate::prelude::*;
use oxiz_sat::Lit;
pub type TheoryId = u8;
#[derive(Debug, Clone)]
pub enum PropagationResult {
None,
Propagated(Vec<Lit>),
Conflict(Vec<Lit>),
}
#[derive(Debug, Clone)]
pub struct Explanation {
pub literal: Lit,
pub reason: Vec<Lit>,
pub theory: TheoryId,
}
pub trait TheoryPropagator: Send + Sync {
fn theory_id(&self) -> TheoryId;
fn propagate(&mut self, assignment: &[Lit]) -> PropagationResult;
fn explain(&self, literal: Lit) -> Option<Explanation>;
fn backtrack(&mut self, level: usize);
fn final_check(&mut self) -> PropagationResult {
PropagationResult::None
}
fn reset(&mut self);
}
#[derive(Debug, Clone)]
pub struct PropagatorConfig {
pub enable_propagation: bool,
pub cache_explanations: bool,
pub max_propagations: usize,
}
impl Default for PropagatorConfig {
fn default() -> Self {
Self {
enable_propagation: true,
cache_explanations: true,
max_propagations: 1000,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PropagatorStats {
pub propagations: u64,
pub conflicts: u64,
pub explanations: u64,
pub backtracks: u64,
}
pub struct PropagatorManager {
propagators: Vec<Box<dyn TheoryPropagator>>,
config: PropagatorConfig,
stats: PropagatorStats,
}
impl PropagatorManager {
pub fn new(config: PropagatorConfig) -> Self {
Self {
propagators: Vec::new(),
config,
stats: PropagatorStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(PropagatorConfig::default())
}
pub fn register(&mut self, propagator: Box<dyn TheoryPropagator>) {
self.propagators.push(propagator);
}
pub fn propagate_all(&mut self, assignment: &[Lit]) -> PropagationResult {
if !self.config.enable_propagation {
return PropagationResult::None;
}
let mut all_propagated = Vec::new();
for propagator in &mut self.propagators {
match propagator.propagate(assignment) {
PropagationResult::Conflict(lits) => {
self.stats.conflicts += 1;
return PropagationResult::Conflict(lits);
}
PropagationResult::Propagated(lits) => {
self.stats.propagations += lits.len() as u64;
all_propagated.extend(lits);
if all_propagated.len() >= self.config.max_propagations {
break;
}
}
PropagationResult::None => {}
}
}
if all_propagated.is_empty() {
PropagationResult::None
} else {
PropagationResult::Propagated(all_propagated)
}
}
pub fn explain(&mut self, literal: Lit) -> Option<Explanation> {
for propagator in &self.propagators {
if let Some(explanation) = propagator.explain(literal) {
self.stats.explanations += 1;
return Some(explanation);
}
}
None
}
pub fn backtrack(&mut self, level: usize) {
for propagator in &mut self.propagators {
propagator.backtrack(level);
}
self.stats.backtracks += 1;
}
pub fn final_check(&mut self) -> PropagationResult {
for propagator in &mut self.propagators {
if let result @ (PropagationResult::Conflict(_) | PropagationResult::Propagated(_)) =
propagator.final_check()
{
return result;
}
}
PropagationResult::None
}
pub fn reset_all(&mut self) {
for propagator in &mut self.propagators {
propagator.reset();
}
}
pub fn stats(&self) -> &PropagatorStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = PropagatorStats::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockPropagator {
id: TheoryId,
}
impl TheoryPropagator for MockPropagator {
fn theory_id(&self) -> TheoryId {
self.id
}
fn propagate(&mut self, _assignment: &[Lit]) -> PropagationResult {
use oxiz_sat::Var;
PropagationResult::Propagated(vec![Lit::pos(Var::new(10))])
}
fn explain(&self, _literal: Lit) -> Option<Explanation> {
use oxiz_sat::Var;
Some(Explanation {
literal: Lit::pos(Var::new(10)),
reason: vec![],
theory: self.id,
})
}
fn backtrack(&mut self, _level: usize) {}
fn reset(&mut self) {}
}
#[test]
fn test_manager_creation() {
let manager = PropagatorManager::default_config();
assert_eq!(manager.stats().propagations, 0);
}
#[test]
fn test_register_propagator() {
let mut manager = PropagatorManager::default_config();
let mock = Box::new(MockPropagator { id: 1 });
manager.register(mock);
assert_eq!(manager.propagators.len(), 1);
}
#[test]
fn test_propagate_all() {
use oxiz_sat::Var;
let mut manager = PropagatorManager::default_config();
manager.register(Box::new(MockPropagator { id: 1 }));
let assignment = vec![Lit::pos(Var::new(0))];
let result = manager.propagate_all(&assignment);
match result {
PropagationResult::Propagated(lits) => {
assert!(!lits.is_empty());
assert_eq!(manager.stats().propagations, lits.len() as u64);
}
_ => panic!("Expected propagation"),
}
}
#[test]
fn test_explain() {
use oxiz_sat::Var;
let mut manager = PropagatorManager::default_config();
manager.register(Box::new(MockPropagator { id: 1 }));
let lit = Lit::pos(Var::new(10));
let explanation = manager.explain(lit);
assert!(explanation.is_some());
assert_eq!(manager.stats().explanations, 1);
}
#[test]
fn test_backtrack() {
let mut manager = PropagatorManager::default_config();
manager.register(Box::new(MockPropagator { id: 1 }));
manager.backtrack(5);
assert_eq!(manager.stats().backtracks, 1);
}
#[test]
fn test_stats() {
let mut manager = PropagatorManager::default_config();
manager.stats.propagations = 100;
assert_eq!(manager.stats().propagations, 100);
manager.reset_stats();
assert_eq!(manager.stats().propagations, 0);
}
}