#![allow(clippy::if_same_then_else)]
#[allow(unused_imports)]
use crate::prelude::*;
pub type TermId = u32;
pub type TheoryId = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Equality {
pub lhs: TermId,
pub rhs: TermId,
}
impl Equality {
pub fn new(lhs: TermId, rhs: TermId) -> Self {
if lhs <= rhs {
Self { lhs, rhs }
} else {
Self { lhs: rhs, rhs: lhs }
}
}
}
#[derive(Debug, Clone)]
pub struct NelsonOppenConfig {
pub convex_optimization: bool,
pub max_iterations: u32,
pub early_termination: bool,
}
impl Default for NelsonOppenConfig {
fn default() -> Self {
Self {
convex_optimization: true,
max_iterations: 1000,
early_termination: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct NelsonOppenStats {
pub iterations: u64,
pub equalities_propagated: u64,
pub theory_calls: u64,
pub conflicts: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CombinationResult {
Sat,
Unsat,
Unknown,
}
pub struct NelsonOppen {
config: NelsonOppenConfig,
stats: NelsonOppenStats,
shared_terms: FxHashSet<TermId>,
implied_equalities: FxHashMap<TheoryId, Vec<Equality>>,
}
impl NelsonOppen {
pub fn new() -> Self {
Self::with_config(NelsonOppenConfig::default())
}
pub fn with_config(config: NelsonOppenConfig) -> Self {
Self {
config,
stats: NelsonOppenStats::default(),
shared_terms: FxHashSet::default(),
implied_equalities: FxHashMap::default(),
}
}
pub fn stats(&self) -> &NelsonOppenStats {
&self.stats
}
pub fn add_shared_term(&mut self, term: TermId) {
self.shared_terms.insert(term);
}
pub fn combine(
&mut self,
theory_ids: &[TheoryId],
initial_equalities: &[Equality],
) -> CombinationResult {
let mut current_equalities: FxHashSet<Equality> =
initial_equalities.iter().copied().collect();
let mut propagation_queue = Vec::new();
for iteration in 0..self.config.max_iterations {
self.stats.iterations += 1;
let mut changed = false;
for &theory_id in theory_ids {
self.stats.theory_calls += 1;
let result = self.check_theory(theory_id, ¤t_equalities);
match result {
CombinationResult::Unsat => {
self.stats.conflicts += 1;
return CombinationResult::Unsat;
}
CombinationResult::Sat => {
let implied = self.get_implied_equalities(theory_id, ¤t_equalities);
for eq in implied {
if current_equalities.insert(eq) {
propagation_queue.push(eq);
self.stats.equalities_propagated += 1;
changed = true;
}
}
}
CombinationResult::Unknown => {}
}
}
if !changed && self.config.early_termination {
return CombinationResult::Sat;
}
if !propagation_queue.is_empty() {
propagation_queue.clear();
}
if iteration > 0 && !changed {
break;
}
}
CombinationResult::Sat
}
fn check_theory(
&mut self,
theory_id: TheoryId,
_equalities: &FxHashSet<Equality>,
) -> CombinationResult {
if theory_id == 0 {
CombinationResult::Sat
} else {
CombinationResult::Sat
}
}
fn get_implied_equalities(
&mut self,
theory_id: TheoryId,
_current_equalities: &FxHashSet<Equality>,
) -> Vec<Equality> {
self.implied_equalities
.get(&theory_id)
.cloned()
.unwrap_or_default()
}
pub fn add_implied_equality(&mut self, theory_id: TheoryId, eq: Equality) {
self.implied_equalities
.entry(theory_id)
.or_default()
.push(eq);
}
pub fn generate_arrangements(&self, terms: &[TermId]) -> Vec<Vec<Equality>> {
if terms.len() <= 1 {
return vec![Vec::new()];
}
vec![Vec::new()]
}
pub fn clear(&mut self) {
self.shared_terms.clear();
self.implied_equalities.clear();
}
}
impl Default for NelsonOppen {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creation() {
let no = NelsonOppen::new();
assert_eq!(no.stats().iterations, 0);
}
#[test]
fn test_equality_normalization() {
let eq1 = Equality::new(1, 2);
let eq2 = Equality::new(2, 1);
assert_eq!(eq1, eq2); }
#[test]
fn test_add_shared_term() {
let mut no = NelsonOppen::new();
no.add_shared_term(1);
no.add_shared_term(2);
assert!(no.shared_terms.contains(&1));
assert!(no.shared_terms.contains(&2));
}
#[test]
fn test_combine_sat() {
let mut no = NelsonOppen::new();
let theories = vec![0, 1];
let equalities = vec![];
let result = no.combine(&theories, &equalities);
assert_eq!(result, CombinationResult::Sat);
assert!(no.stats().iterations > 0);
}
#[test]
fn test_add_implied_equality() {
let mut no = NelsonOppen::new();
let eq = Equality::new(1, 2);
no.add_implied_equality(0, eq);
let implied = no.get_implied_equalities(0, &FxHashSet::default());
assert_eq!(implied.len(), 1);
assert_eq!(implied[0], eq);
}
#[test]
fn test_clear() {
let mut no = NelsonOppen::new();
no.add_shared_term(1);
no.add_implied_equality(0, Equality::new(1, 2));
no.clear();
assert!(no.shared_terms.is_empty());
assert!(no.implied_equalities.is_empty());
}
#[test]
fn test_generate_arrangements() {
let no = NelsonOppen::new();
let terms = vec![1, 2, 3];
let arrangements = no.generate_arrangements(&terms);
assert!(!arrangements.is_empty());
}
}