use std::collections::{HashMap, HashSet};
use std::fmt;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Literal(pub i32);
#[allow(dead_code)]
impl Literal {
#[must_use]
pub fn pos(var: u32) -> Self {
Self(var as i32)
}
#[must_use]
pub fn neg(var: u32) -> Self {
Self(-(var as i32))
}
#[must_use]
pub fn var(self) -> u32 {
self.0.unsigned_abs()
}
#[must_use]
pub fn is_positive(self) -> bool {
self.0 > 0
}
#[must_use]
pub fn negate(self) -> Self {
Self(-self.0)
}
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Clause {
pub literals: Vec<Literal>,
}
#[allow(dead_code)]
impl Clause {
#[must_use]
pub fn new(literals: Vec<Literal>) -> Self {
Self { literals }
}
#[must_use]
pub fn empty() -> Self {
Self {
literals: Vec::new(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.literals.is_empty()
}
#[must_use]
pub fn is_unit(&self) -> bool {
self.literals.len() == 1
}
#[must_use]
pub fn unit_literal(&self) -> Option<Literal> {
if self.is_unit() {
Some(self.literals[0])
} else {
None
}
}
#[must_use]
pub fn contains(&self, lit: Literal) -> bool {
self.literals.contains(&lit)
}
}
impl fmt::Display for Clause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(")?;
for (i, lit) in self.literals.iter().enumerate() {
if i > 0 {
write!(f, " ∨ ")?;
}
write!(f, "{}", lit)?;
}
write!(f, ")")
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ResolutionStepId(pub u32);
impl fmt::Display for ResolutionStepId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "r{}", self.0)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ResolutionStep {
pub id: ResolutionStepId,
pub clause: Clause,
pub rule: ResolutionRule,
pub premises: Vec<ResolutionStepId>,
pub pivot: Option<Literal>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolutionRule {
Input,
Resolution,
UnitPropagation,
Learn,
}
impl fmt::Display for ResolutionRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input => write!(f, "input"),
Self::Resolution => write!(f, "resolution"),
Self::UnitPropagation => write!(f, "unit_prop"),
Self::Learn => write!(f, "learn"),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Default)]
pub struct ResolutionProof {
steps: Vec<ResolutionStep>,
next_id: u32,
clause_map: HashMap<Clause, ResolutionStepId>,
}
#[allow(dead_code)]
impl ResolutionProof {
#[must_use]
pub fn new() -> Self {
Self {
steps: Vec::new(),
next_id: 0,
clause_map: HashMap::new(),
}
}
pub fn add_input(&mut self, clause: Clause) -> ResolutionStepId {
if let Some(&id) = self.clause_map.get(&clause) {
return id;
}
let id = ResolutionStepId(self.next_id);
self.next_id += 1;
self.steps.push(ResolutionStep {
id,
clause: clause.clone(),
rule: ResolutionRule::Input,
premises: Vec::new(),
pivot: None,
});
self.clause_map.insert(clause, id);
id
}
pub fn resolve(
&mut self,
c1: ResolutionStepId,
c2: ResolutionStepId,
pivot: Literal,
) -> Result<ResolutionStepId, String> {
let clause1 = self.get_clause(c1).ok_or("Invalid premise 1")?;
let clause2 = self.get_clause(c2).ok_or("Invalid premise 2")?;
if !clause1.contains(pivot) {
return Err(format!("Clause {} does not contain pivot {}", c1, pivot));
}
if !clause2.contains(pivot.negate()) {
return Err(format!(
"Clause {} does not contain negated pivot {}",
c2,
pivot.negate()
));
}
let mut resolvent_lits: HashSet<Literal> = HashSet::new();
for &lit in &clause1.literals {
if lit != pivot {
resolvent_lits.insert(lit);
}
}
for &lit in &clause2.literals {
if lit != pivot.negate() {
resolvent_lits.insert(lit);
}
}
let mut final_lits = Vec::new();
for &lit in &resolvent_lits {
if !resolvent_lits.contains(&lit.negate()) {
final_lits.push(lit);
} else if lit.is_positive() {
continue;
}
}
let resolvent = Clause::new(final_lits);
if let Some(&id) = self.clause_map.get(&resolvent) {
return Ok(id);
}
let id = ResolutionStepId(self.next_id);
self.next_id += 1;
self.steps.push(ResolutionStep {
id,
clause: resolvent.clone(),
rule: ResolutionRule::Resolution,
premises: vec![c1, c2],
pivot: Some(pivot),
});
self.clause_map.insert(resolvent, id);
Ok(id)
}
pub fn unit_propagate(
&mut self,
unit_clause: ResolutionStepId,
clause: ResolutionStepId,
) -> Result<ResolutionStepId, String> {
let unit = self.get_clause(unit_clause).ok_or("Invalid unit clause")?;
if !unit.is_unit() {
return Err("First premise must be a unit clause".to_string());
}
let pivot = unit
.unit_literal()
.expect("unit clause has exactly one literal");
self.resolve(unit_clause, clause, pivot)
}
#[must_use]
pub fn get_clause(&self, id: ResolutionStepId) -> Option<&Clause> {
self.steps.get(id.0 as usize).map(|step| &step.clause)
}
#[must_use]
pub fn get_step(&self, id: ResolutionStepId) -> Option<&ResolutionStep> {
self.steps.get(id.0 as usize)
}
#[must_use]
pub fn steps(&self) -> &[ResolutionStep] {
&self.steps
}
#[must_use]
pub fn len(&self) -> usize {
self.steps.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
#[must_use]
pub fn derives_empty_clause(&self) -> bool {
self.steps.iter().any(|step| step.clause.is_empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_literal_creation() {
let lit = Literal::pos(5);
assert_eq!(lit.0, 5);
assert!(lit.is_positive());
assert_eq!(lit.var(), 5);
let neg_lit = Literal::neg(5);
assert_eq!(neg_lit.0, -5);
assert!(!neg_lit.is_positive());
assert_eq!(neg_lit.var(), 5);
}
#[test]
fn test_literal_negation() {
let lit = Literal::pos(3);
let neg = lit.negate();
assert_eq!(neg, Literal::neg(3));
assert_eq!(neg.negate(), lit);
}
#[test]
fn test_clause_creation() {
let clause = Clause::new(vec![Literal::pos(1), Literal::neg(2)]);
assert_eq!(clause.literals.len(), 2);
assert!(!clause.is_empty());
assert!(!clause.is_unit());
}
#[test]
fn test_empty_clause() {
let clause = Clause::empty();
assert!(clause.is_empty());
assert!(!clause.is_unit());
}
#[test]
fn test_unit_clause() {
let clause = Clause::new(vec![Literal::pos(1)]);
assert!(clause.is_unit());
assert_eq!(clause.unit_literal(), Some(Literal::pos(1)));
}
#[test]
fn test_resolution_proof_input() {
let mut proof = ResolutionProof::new();
let clause = Clause::new(vec![Literal::pos(1), Literal::neg(2)]);
let id = proof.add_input(clause.clone());
assert_eq!(proof.len(), 1);
assert_eq!(proof.get_clause(id), Some(&clause));
}
#[test]
fn test_resolution_simple() {
let mut proof = ResolutionProof::new();
let c1 = proof.add_input(Clause::new(vec![Literal::pos(1), Literal::pos(2)]));
let c2 = proof.add_input(Clause::new(vec![Literal::neg(1), Literal::pos(3)]));
let result = proof.resolve(c1, c2, Literal::pos(1));
assert!(result.is_ok());
let resolvent_id = result.expect("test operation should succeed");
let resolvent = proof
.get_clause(resolvent_id)
.expect("test operation should succeed");
assert_eq!(resolvent.literals.len(), 2);
assert!(resolvent.contains(Literal::pos(2)));
assert!(resolvent.contains(Literal::pos(3)));
}
#[test]
fn test_resolution_to_empty() {
let mut proof = ResolutionProof::new();
let c1 = proof.add_input(Clause::new(vec![Literal::pos(1)]));
let c2 = proof.add_input(Clause::new(vec![Literal::neg(1)]));
let result = proof.resolve(c1, c2, Literal::pos(1));
assert!(result.is_ok());
let empty_id = result.expect("test operation should succeed");
let empty = proof
.get_clause(empty_id)
.expect("test operation should succeed");
assert!(empty.is_empty());
assert!(proof.derives_empty_clause());
}
#[test]
fn test_unit_propagation() {
let mut proof = ResolutionProof::new();
let unit = proof.add_input(Clause::new(vec![Literal::pos(1)]));
let clause = proof.add_input(Clause::new(vec![Literal::neg(1), Literal::pos(2)]));
let result = proof.unit_propagate(unit, clause);
assert!(result.is_ok());
let derived = result.expect("test operation should succeed");
let derived_clause = proof
.get_clause(derived)
.expect("test operation should succeed");
assert!(derived_clause.is_unit());
assert_eq!(derived_clause.unit_literal(), Some(Literal::pos(2)));
}
#[test]
fn test_resolution_invalid_pivot() {
let mut proof = ResolutionProof::new();
let c1 = proof.add_input(Clause::new(vec![Literal::pos(1), Literal::pos(2)]));
let c2 = proof.add_input(Clause::new(vec![Literal::pos(3), Literal::pos(4)]));
let result = proof.resolve(c1, c2, Literal::pos(5));
assert!(result.is_err());
}
mod proptests {
use super::*;
use proptest::prelude::*;
fn var_id() -> impl Strategy<Value = u32> {
1..20_u32
}
fn literal() -> impl Strategy<Value = Literal> {
(var_id(), any::<bool>()).prop_map(|(var, sign)| {
if sign {
Literal::pos(var)
} else {
Literal::neg(var)
}
})
}
fn clause() -> impl Strategy<Value = Clause> {
prop::collection::vec(literal(), 0..6).prop_map(Clause::new)
}
proptest! {
#[test]
fn prop_literal_double_negation(var in var_id()) {
let lit = Literal::pos(var);
let neg = lit.negate();
let double_neg = neg.negate();
prop_assert_eq!(lit, double_neg);
}
#[test]
fn prop_literal_same_variable(var in var_id()) {
let pos = Literal::pos(var);
let neg = Literal::neg(var);
prop_assert_eq!(pos.var(), neg.var());
}
#[test]
fn prop_complementary_opposite_sign(var in var_id()) {
let pos = Literal::pos(var);
let neg = Literal::neg(var);
prop_assert_eq!(pos.negate(), neg);
prop_assert_eq!(neg.negate(), pos);
prop_assert_ne!(pos.is_positive(), neg.is_positive());
}
#[test]
fn prop_add_input_increases_size(c in clause()) {
let mut proof = ResolutionProof::new();
let initial_len = proof.len();
proof.add_input(c);
prop_assert!(proof.len() > initial_len);
}
#[test]
fn prop_empty_clause_check(literals in prop::collection::vec(literal(), 0..5)) {
let clause = Clause::new(literals.clone());
prop_assert_eq!(clause.is_empty(), literals.is_empty());
}
#[test]
fn prop_unit_clause_one_literal(lit in literal()) {
let clause = Clause::new(vec![lit]);
prop_assert!(clause.is_unit());
prop_assert_eq!(clause.unit_literal(), Some(lit));
}
#[test]
fn prop_clause_length(literals in prop::collection::vec(literal(), 0..8)) {
let clause = Clause::new(literals.clone());
prop_assert!(clause.literals.len() <= literals.len());
}
#[test]
fn prop_clause_contains_literals(literals in prop::collection::vec(literal(), 1..6)) {
let clause = Clause::new(literals.clone());
for lit in &literals {
let _ = clause.contains(*lit);
}
}
#[test]
fn prop_resolution_invalid_pivot(
c1 in clause(),
c2 in clause(),
pivot_var in var_id()
) {
let mut proof = ResolutionProof::new();
let id1 = proof.add_input(c1.clone());
let id2 = proof.add_input(c2.clone());
let pivot = Literal::pos(pivot_var);
let has_pos = c1.contains(pivot) && c2.contains(pivot.negate());
let has_neg = c1.contains(pivot.negate()) && c2.contains(pivot);
if !has_pos && !has_neg {
let result = proof.resolve(id1, id2, pivot);
let _ = result;
}
}
#[test]
fn prop_unit_resolution_reduces(var in var_id()) {
let mut proof = ResolutionProof::new();
let c1 = proof.add_input(Clause::new(vec![Literal::pos(var)]));
let c2 = proof.add_input(Clause::new(vec![Literal::neg(var)]));
let result = proof.resolve(c1, c2, Literal::pos(var));
if let Ok(resolvent_id) = result {
let resolvent = proof.get_clause(resolvent_id).expect("test operation should succeed");
prop_assert!(resolvent.is_empty());
}
}
#[test]
fn prop_duplicate_clauses_dedup(c in clause()) {
let mut proof = ResolutionProof::new();
proof.add_input(c.clone());
let len1 = proof.len();
proof.add_input(c);
let len2 = proof.len();
prop_assert!(len2 >= len1);
}
#[test]
fn prop_complementary_in_clause(var in var_id()) {
let clause = Clause::new(vec![Literal::pos(var), Literal::neg(var)]);
prop_assert!(clause.contains(Literal::pos(var)));
prop_assert!(clause.contains(Literal::neg(var)));
}
#[test]
fn prop_clause_literal_access(literals in prop::collection::vec(literal(), 1..8)) {
let clause = Clause::new(literals);
for lit in &clause.literals {
let _ = lit.var();
}
}
}
}
}