use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeSet, HashSet};
use std::hash::{Hash, Hasher};
use std::num::Wrapping;
use itertools::Itertools;
use crate::formulas::{EncodedFormula, FormulaFactory, Literal, StringLiteral, Variable};
use super::model::Model;
#[derive(Debug, Clone)]
pub struct Assignment {
pub pos: HashSet<Variable>,
pub neg: HashSet<Variable>,
}
impl Assignment {
pub const fn new(pos: HashSet<Variable>, neg: HashSet<Variable>) -> Self {
Self { pos, neg }
}
pub fn from_variables(pos: &[Variable], neg: &[Variable]) -> Self {
Self { pos: pos.iter().copied().collect::<HashSet<Variable>>(), neg: neg.iter().copied().collect::<HashSet<Variable>>() }
}
pub fn from_lit(lit: Literal) -> Self {
if lit.phase() {
Self { pos: vec![lit.variable()].into_iter().collect(), neg: HashSet::new() }
} else {
Self { pos: HashSet::new(), neg: vec![lit.variable()].into_iter().collect() }
}
}
pub fn from_literals(literals: &[Literal]) -> Self {
let mut pos = HashSet::with_capacity(literals.len());
let mut neg = HashSet::with_capacity(literals.len());
for lit in literals {
if lit.phase() {
pos.insert(lit.variable());
} else {
neg.insert(lit.variable());
}
}
Self { pos, neg }
}
pub fn from_set(set: BTreeSet<Literal>) -> Self {
let mut pos = HashSet::new();
let mut neg = HashSet::new();
for lit in set {
if lit.phase() {
pos.insert(lit.variable());
} else {
neg.insert(lit.variable());
}
}
Self { pos, neg }
}
pub fn from_names(pos: &[&str], neg: &[&str], f: &FormulaFactory) -> Result<Self, String> {
let pos = names_to_indices(pos, f)?;
let neg = names_to_indices(neg, f)?;
Ok(Self { pos, neg })
}
pub const fn pos(&self) -> &HashSet<Variable> {
&self.pos
}
pub const fn neg(&self) -> &HashSet<Variable> {
&self.neg
}
pub fn len(&self) -> usize {
self.pos.len() + self.neg.len()
}
pub fn is_empty(&self) -> bool {
self.pos.is_empty() && self.neg.is_empty()
}
pub fn contains_pos(&self, var: Variable) -> bool {
self.pos.contains(&var)
}
pub fn contains_neg(&self, var: Variable) -> bool {
self.neg.contains(&var)
}
pub fn contains(&self, lit: Literal) -> bool {
if lit.phase() {
self.pos.contains(&lit.variable())
} else {
self.neg.contains(&lit.variable())
}
}
pub fn evaluate_lit(&self, lit: Literal) -> bool {
let var = &lit.variable();
if lit.phase() {
self.pos.contains(var)
} else {
self.neg.contains(var) || !self.pos.contains(var)
}
}
pub fn restrict_lit(&self, lit: Literal) -> EncodedFormula {
let var = lit.variable();
let phase = lit.phase();
if self.pos.contains(&var) {
if phase {
EncodedFormula::constant(true)
} else {
EncodedFormula::constant(false)
}
} else if self.neg.contains(&var) {
if phase {
EncodedFormula::constant(false)
} else {
EncodedFormula::constant(true)
}
} else {
lit.into()
}
}
#[allow(clippy::option_if_let_else)] pub fn blocking_clause(&self, f: &FormulaFactory, variables: Option<&[Variable]>) -> EncodedFormula {
let ops = if let Some(variables) = variables {
variables
.iter()
.filter_map(|v| {
if self.pos.contains(v) {
Some(EncodedFormula::from(v.neg_lit()))
} else if self.neg.contains(v) {
Some(EncodedFormula::from(v.pos_lit()))
} else {
None
}
})
.collect()
} else {
let mut ops: Vec<EncodedFormula> = self.pos.iter().map(|v| EncodedFormula::from(v.neg_lit())).collect();
ops.append(&mut self.neg.iter().map(|v| EncodedFormula::from(v.pos_lit())).collect());
ops
};
f.or(&ops)
}
pub fn literals(&self) -> Vec<Literal> {
let mut result = Vec::with_capacity(self.len());
self.pos.iter().for_each(|var| result.push(Literal::Pos(*var)));
self.neg.iter().for_each(|var| result.push(Literal::Neg(*var)));
result
}
pub fn string_literals<'a>(&self, f: &'a FormulaFactory) -> Vec<StringLiteral<'a>> {
let mut result = Vec::with_capacity(self.len());
self.pos.iter().for_each(|var| result.push(Literal::Pos(*var).to_string_lit(f)));
self.neg.iter().for_each(|var| result.push(Literal::Neg(*var).to_string_lit(f)));
result
}
pub fn to_string(&self, f: &FormulaFactory) -> String {
let pos = self.pos.iter().map(|v| v.name(f)).join(", ");
let neg = self.neg.iter().map(|v| v.name(f)).join(", ");
format!("Assignment{{pos=[{pos}], neg=[{neg}]}}")
}
}
impl Hash for Assignment {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.pos.iter().map(|&v| var_hash(v)).sum::<Wrapping<u64>>().0);
state.write_u64(self.neg.iter().map(|&v| var_hash(v)).sum::<Wrapping<u64>>().0);
}
}
impl PartialEq for Assignment {
fn eq(&self, other: &Self) -> bool {
self.pos == other.pos && self.neg == other.neg
}
}
impl Eq for Assignment {}
impl<A: AsRef<Assignment>> From<A> for Model {
fn from(assignment: A) -> Self {
Self::new(assignment.as_ref().pos.iter().copied().collect::<Vec<_>>(), assignment.as_ref().neg.iter().copied().collect::<Vec<_>>())
}
}
impl From<Assignment> for Model {
fn from(assignment: Assignment) -> Self {
Self::new(assignment.pos.into_iter().collect::<Vec<_>>(), assignment.neg.into_iter().collect::<Vec<_>>())
}
}
fn var_hash(var: Variable) -> Wrapping<u64> {
let hasher = &mut DefaultHasher::new();
var.hash(hasher);
Wrapping(hasher.finish())
}
fn names_to_indices(names: &[&str], f: &FormulaFactory) -> Result<HashSet<Variable>, String> {
let mut result = HashSet::with_capacity(names.len());
for name in names {
let index = match f.variables.lookup(&(*name).to_string()) {
Some(i) => Variable::FF(i),
None => {
return Err(format!("Variable {} is not known in the given FormulaFactory", *name));
}
};
result.insert(index);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use crate::datastructures::Assignment;
use crate::formulas::{FormulaFactory, ToFormula};
use crate::util::test_util::{lits, vars_list};
#[test]
fn test_constructor1() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b c", f);
let vars2 = vars_list("x y", f);
let a1 = Assignment::new(vars1.iter().copied().collect(), vars2.iter().copied().collect());
assert!(a1.contains_pos(f.var("a")));
assert!(a1.contains_pos(f.var("b")));
assert!(a1.contains_pos(f.var("c")));
assert!(!a1.contains_pos(f.var("d")));
assert!(!a1.contains_pos(f.var("x")));
assert!(!a1.contains_pos(f.var("y")));
assert!(!a1.contains_neg(f.var("a")));
assert!(!a1.contains_neg(f.var("b")));
assert!(!a1.contains_neg(f.var("c")));
assert!(!a1.contains_neg(f.var("d")));
assert!(a1.contains_neg(f.var("x")));
assert!(a1.contains_neg(f.var("y")));
assert_eq!(5, a1.len());
}
#[test]
fn test_constructor2() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b c", f);
let vars2 = vars_list("x y", f);
let a1 = Assignment::from_variables(&vars1, &vars2);
assert!(a1.contains_pos(f.var("a")));
assert!(a1.contains_pos(f.var("b")));
assert!(a1.contains_pos(f.var("c")));
assert!(!a1.contains_pos(f.var("d")));
assert!(!a1.contains_pos(f.var("x")));
assert!(!a1.contains_pos(f.var("y")));
assert!(!a1.contains_neg(f.var("a")));
assert!(!a1.contains_neg(f.var("b")));
assert!(!a1.contains_neg(f.var("c")));
assert!(!a1.contains_neg(f.var("d")));
assert!(a1.contains_neg(f.var("x")));
assert!(a1.contains_neg(f.var("y")));
assert_eq!(5, a1.len());
}
#[test]
fn test_constructor3() {
let f = &FormulaFactory::new();
let lits = lits("a b c ~x ~y", f);
let a1 = Assignment::from_set(lits);
assert!(a1.contains_pos(f.var("a")));
assert!(a1.contains_pos(f.var("b")));
assert!(a1.contains_pos(f.var("c")));
assert!(!a1.contains_pos(f.var("d")));
assert!(!a1.contains_pos(f.var("x")));
assert!(!a1.contains_pos(f.var("y")));
assert!(!a1.contains_neg(f.var("a")));
assert!(!a1.contains_neg(f.var("b")));
assert!(!a1.contains_neg(f.var("c")));
assert!(!a1.contains_neg(f.var("d")));
assert!(a1.contains_neg(f.var("x")));
assert!(a1.contains_neg(f.var("y")));
assert_eq!(5, a1.len());
}
#[test]
fn test_constructor4() {
let f = &FormulaFactory::new();
let a1 = Assignment::from_lit(f.lit("a", true));
assert!(a1.contains_pos(f.var("a")));
assert!(!a1.contains_pos(f.var("b")));
assert!(!a1.contains_neg(f.var("a")));
assert!(!a1.contains_neg(f.var("b")));
assert_eq!(1, a1.len());
let a2 = Assignment::from_lit(f.lit("a", false));
assert!(!a2.contains_pos(f.var("a")));
assert!(!a2.contains_pos(f.var("b")));
assert!(a2.contains_neg(f.var("a")));
assert!(!a2.contains_neg(f.var("b")));
assert_eq!(1, a2.len());
}
#[test]
fn test_is_empty() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b", f);
let vars2 = vars_list("x y", f);
let a1 = Assignment::from_variables(&[], &[]);
let a2 = Assignment::from_variables(&vars1, &[]);
let a3 = Assignment::from_variables(&[], &vars2);
let a4 = Assignment::from_variables(&vars1, &vars2);
assert!(a1.is_empty());
assert!(!a2.is_empty());
assert!(!a3.is_empty());
assert!(!a4.is_empty());
}
#[test]
fn test_contains() {
let f = &FormulaFactory::new();
let lits = lits("a b c ~x ~y", f);
let a1 = Assignment::from_set(lits);
assert!(a1.contains(f.lit("a", true)));
assert!(a1.contains(f.lit("b", true)));
assert!(a1.contains(f.lit("c", true)));
assert!(a1.contains(f.lit("x", false)));
assert!(a1.contains(f.lit("y", false)));
assert!(!a1.contains(f.lit("a", false)));
assert!(!a1.contains(f.lit("b", false)));
assert!(!a1.contains(f.lit("c", false)));
assert!(!a1.contains(f.lit("x", true)));
assert!(!a1.contains(f.lit("y", true)));
assert!(!a1.contains(f.lit("d", true)));
assert!(!a1.contains(f.lit("d", false)));
}
#[test]
fn test_evaluate_lit() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b", f);
let vars2 = vars_list("x", f);
let a1 = Assignment::from_variables(&vars1, &vars2);
assert!(a1.evaluate_lit(f.lit("a", true)));
assert!(!a1.evaluate_lit(f.lit("a", false)));
assert!(a1.evaluate_lit(f.lit("b", true)));
assert!(!a1.evaluate_lit(f.lit("b", false)));
assert!(!a1.evaluate_lit(f.lit("x", true)));
assert!(a1.evaluate_lit(f.lit("x", false)));
assert!(!a1.evaluate_lit(f.lit("d", true)));
assert!(a1.evaluate_lit(f.lit("d", false)));
}
#[test]
fn test_restrict_lit() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b", f);
let vars2 = vars_list("x", f);
let a1 = Assignment::from_variables(&vars1, &vars2);
assert_eq!(f.verum(), a1.restrict_lit(f.lit("a", true)));
assert_eq!(f.falsum(), a1.restrict_lit(f.lit("a", false)));
assert_eq!(f.verum(), a1.restrict_lit(f.lit("b", true)));
assert_eq!(f.falsum(), a1.restrict_lit(f.lit("b", false)));
assert_eq!(f.falsum(), a1.restrict_lit(f.lit("x", true)));
assert_eq!(f.verum(), a1.restrict_lit(f.lit("x", false)));
assert_eq!(f.literal("d", true), a1.restrict_lit(f.lit("d", true)));
assert_eq!(f.literal("d", false), a1.restrict_lit(f.lit("d", false)));
}
#[test]
fn test_blocking_clause() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b", f);
let vars2 = vars_list("x y", f);
let a1 = Assignment::from_variables(&vars1, &vars2);
assert_eq!("~a | ~b | x | y".to_formula(f), a1.blocking_clause(f, None));
let bc_vars = vars_list("a x c", f);
assert_eq!("~a | x".to_formula(f), a1.blocking_clause(f, Some(&bc_vars)));
}
#[test]
fn test_to_string() {
let f = &FormulaFactory::new();
let vars1 = vars_list("a b", f);
let vars2 = vars_list("x y", f);
let a1 = Assignment::from_variables(&[], &[]);
let a2 = Assignment::from_variables(&vars1, &[]);
let a3 = Assignment::from_variables(&[], &vars2);
let a4 = Assignment::from_variables(&vars1, &vars2);
assert_eq!("Assignment{pos=[], neg=[]}", a1.to_string(f));
assert!(a2.to_string(f) == "Assignment{pos=[a, b], neg=[]}" || a2.to_string(f) == "Assignment{pos=[b, a], neg=[]}");
assert!(a3.to_string(f) == "Assignment{pos=[], neg=[x, y]}" || a3.to_string(f) == "Assignment{pos=[], neg=[y, x]}");
assert!(
a4.to_string(f) == "Assignment{pos=[a, b], neg=[x, y]}"
|| a4.to_string(f) == "Assignment{pos=[b, a], neg=[x, y]}"
|| a4.to_string(f) == "Assignment{pos=[a, b], neg=[y, x]}"
|| a4.to_string(f) == "Assignment{pos=[b, a], neg=[y, x]}"
);
}
}