| //! 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); |
| } |
| } |
| } |