blob: 517f03be2e73cf20a119ac6b4645ffae46729efd [file] [log] [blame] [edit]
//! Unit propagation.
use partial_ref::{partial, PartialRef};
use crate::context::{parts::*, Context};
pub mod assignment;
pub mod binary;
pub mod graph;
pub mod long;
pub mod watch;
pub use assignment::{backtrack, enqueue_assignment, full_restart, restart, Assignment, Trail};
pub use graph::{Conflict, ImplGraph, ImplNode, Reason};
pub use watch::{enable_watchlists, Watch, Watchlists};
/// Propagate enqueued assignments.
///
/// Returns when all enqueued assignments are propagated, including newly propagated assignemnts, or
/// if there is a conflict.
///
/// On conflict the first propagation that would assign the opposite value to an already assigned
/// literal is returned.
pub fn propagate(
mut ctx: partial!(
Context,
mut AssignmentP,
mut ClauseAllocP,
mut ImplGraphP,
mut TrailP,
mut WatchlistsP,
BinaryClausesP,
ClauseDbP,
),
) -> Result<(), Conflict> {
enable_watchlists(ctx.borrow());
while let Some(lit) = ctx.part_mut(TrailP).pop_queue() {
binary::propagate_binary(ctx.borrow(), lit)?;
long::propagate_long(ctx.borrow(), lit)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::{prelude::*, *};
use rand::{distributions::Bernoulli, seq::SliceRandom};
use partial_ref::IntoPartialRefMut;
use varisat_formula::{cnf::strategy::*, CnfFormula, Lit};
use crate::{
clause::{db, gc},
load::load_clause,
state::SatState,
};
/// Generate a random formula and list of implied literals.
pub fn prop_formula(
vars: impl Strategy<Value = usize>,
extra_vars: impl Strategy<Value = usize>,
extra_clauses: impl Strategy<Value = usize>,
density: impl Strategy<Value = f64>,
) -> impl Strategy<Value = (Vec<Lit>, CnfFormula)> {
(vars, extra_vars, extra_clauses, density).prop_flat_map(
|(vars, extra_vars, extra_clauses, density)| {
let polarity = collection::vec(bool::ANY, vars + extra_vars);
let dist = Bernoulli::new(density).unwrap();
let lits = polarity
.prop_map(|polarity| {
polarity
.into_iter()
.enumerate()
.map(|(index, polarity)| Lit::from_index(index, polarity))
.collect::<Vec<_>>()
})
.prop_shuffle();
lits.prop_perturb(move |mut lits, mut rng| {
let assigned_lits = &lits[..vars];
let mut clauses: Vec<Vec<Lit>> = vec![];
for (i, &lit) in assigned_lits.iter().enumerate() {
// Build a clause that implies lit
let mut clause = vec![lit];
for &reason_lit in assigned_lits[..i].iter() {
if rng.sample(dist) {
clause.push(!reason_lit);
}
}
clause.shuffle(&mut rng);
clauses.push(clause);
}
for _ in 0..extra_clauses {
// Build a clause that is satisfied
let &true_lit = assigned_lits.choose(&mut rng).unwrap();
let mut clause = vec![true_lit];
for &other_lit in lits.iter() {
if other_lit != true_lit && rng.sample(dist) {
clause.push(other_lit ^ rng.gen::<bool>());
}
}
clause.shuffle(&mut rng);
clauses.push(clause);
}
clauses.shuffle(&mut rng);
// Only return implied lits
lits.drain(vars..);
(lits, CnfFormula::from(clauses))
})
},
)
}
proptest! {
#[test]
fn propagation_no_conflict(
(mut lits, formula) in prop_formula(
2..30usize,
0..10usize,
0..20usize,
0.1..0.9
),
) {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
}
prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
let prop_result = propagate(ctx.borrow());
prop_assert_eq!(prop_result, Ok(()));
lits.sort();
let mut prop_lits = ctx.part(TrailP).trail().to_owned();
// Remap vars
for lit in prop_lits.iter_mut() {
*lit = lit.map_var(|solver_var| {
ctx.part(VariablesP).existing_user_from_solver(solver_var)
});
}
prop_lits.sort();
prop_assert_eq!(prop_lits, lits);
}
#[test]
fn propagation_conflict(
(lits, formula) in prop_formula(
2..30usize,
0..10usize,
0..20usize,
0.1..0.9
),
conflict_size in any::<sample::Index>(),
) {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
// We add the conflict clause first to make sure that it isn't simplified during loading
let conflict_size = conflict_size.index(lits.len() - 1) + 2;
let conflict_clause: Vec<_> = lits[..conflict_size].iter().map(|&lit| !lit).collect();
load_clause(ctx.borrow(), &conflict_clause);
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
}
prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
let prop_result = propagate(ctx.borrow());
prop_assert!(prop_result.is_err());
let conflict = prop_result.unwrap_err();
let conflict_lits = conflict.lits(&ctx.borrow()).to_owned();
for &lit in conflict_lits.iter() {
prop_assert!(ctx.part(AssignmentP).lit_is_false(lit));
}
}
#[test]
fn propagation_no_conflict_after_gc(
tmp_formula in cnf_formula(3..30usize, 30..100, 3..30),
(mut lits, formula) in prop_formula(
2..30usize,
0..10usize,
0..20usize,
0.1..0.9
),
) {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
for clause in tmp_formula.iter() {
// Only add long clauses here
let mut lits = clause.to_owned();
lits.sort();
lits.dedup();
if lits.len() >= 3 {
load_clause(ctx.borrow(), clause);
}
}
let tmp_crefs: Vec<_> = db::clauses_iter(&ctx.borrow()).collect();
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
}
for cref in tmp_crefs {
db::delete_clause(ctx.borrow(), cref);
}
gc::collect_garbage(ctx.borrow());
prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
let prop_result = propagate(ctx.borrow());
prop_assert_eq!(prop_result, Ok(()));
lits.sort();
let mut prop_lits = ctx.part(TrailP).trail().to_owned();
// Remap vars
for lit in prop_lits.iter_mut() {
*lit = lit.map_var(|solver_var| {
ctx.part(VariablesP).existing_user_from_solver(solver_var)
});
}
prop_lits.sort();
prop_assert_eq!(prop_lits, lits);
}
}
}