blob: 88ee76d6acdcf2097c872342716b6c6b5eb7af40 [file] [log] [blame] [edit]
//! Conflict driven clause learning.
use partial_ref::{partial, PartialRef};
use varisat_internal_proof::ProofStep;
use crate::{
analyze_conflict::analyze_conflict,
assumptions::{enqueue_assumption, EnqueueAssumption},
clause::{assess_learned_clause, bump_clause, db, decay_clause_activities},
context::{parts::*, Context},
decision::make_decision,
model::reconstruct_global_model,
proof,
prop::{backtrack, enqueue_assignment, propagate, Conflict, Reason},
state::SatState,
unit_simplify::{prove_units, unit_simplify},
};
/// Find a conflict, learn a clause and backtrack.
pub fn conflict_step<'a>(
mut ctx: partial!(
Context<'a>,
mut AnalyzeConflictP,
mut AssignmentP,
mut AssumptionsP,
mut BinaryClausesP,
mut ClauseActivityP,
mut ClauseAllocP,
mut ClauseDbP,
mut ImplGraphP,
mut ModelP,
mut ProofP<'a>,
mut SolverStateP,
mut TmpDataP,
mut TmpFlagsP,
mut TrailP,
mut VariablesP,
mut VsidsP,
mut WatchlistsP,
),
) {
let conflict = find_conflict(ctx.borrow());
let conflict = match conflict {
Ok(()) => {
reconstruct_global_model(ctx.borrow());
return;
}
Err(FoundConflict::Assumption) => {
ctx.part_mut(SolverStateP).sat_state = SatState::UnsatUnderAssumptions;
return;
}
Err(FoundConflict::Conflict(conflict)) => conflict,
};
let backtrack_to = analyze_conflict(ctx.borrow(), conflict);
let (analyze, mut ctx) = ctx.split_part(AnalyzeConflictP);
for &cref in analyze.involved() {
bump_clause(ctx.borrow(), cref);
}
decay_clause_activities(ctx.borrow());
backtrack(ctx.borrow(), backtrack_to);
let clause = analyze.clause();
proof::add_step(
ctx.borrow(),
true,
&ProofStep::AtClause {
redundant: clause.len() > 2,
clause,
propagation_hashes: analyze.clause_hashes(),
},
);
let reason = match clause.len() {
0 => {
ctx.part_mut(SolverStateP).sat_state = SatState::Unsat;
return;
}
1 => Reason::Unit,
2 => {
ctx.part_mut(BinaryClausesP)
.add_binary_clause([clause[0], clause[1]]);
Reason::Binary([clause[1]])
}
_ => {
let header = assess_learned_clause(ctx.borrow(), clause);
let cref = db::add_clause(ctx.borrow(), header, clause);
Reason::Long(cref)
}
};
enqueue_assignment(ctx.borrow(), clause[0], reason);
}
/// Return type of [`find_conflict`].
///
/// Specifies whether a conflict was found during propagation or while enqueuing assumptions.
enum FoundConflict {
Conflict(Conflict),
Assumption,
}
impl From<Conflict> for FoundConflict {
fn from(conflict: Conflict) -> FoundConflict {
FoundConflict::Conflict(conflict)
}
}
/// Find a conflict.
///
/// Returns `Err` if a conflict was found and `Ok` if a satisfying assignment was found instead.
fn find_conflict<'a>(
mut ctx: partial!(
Context<'a>,
mut AssignmentP,
mut AssumptionsP,
mut BinaryClausesP,
mut ClauseAllocP,
mut ClauseDbP,
mut ImplGraphP,
mut ProofP<'a>,
mut SolverStateP,
mut TmpFlagsP,
mut TrailP,
mut VariablesP,
mut VsidsP,
mut WatchlistsP,
),
) -> Result<(), FoundConflict> {
loop {
let propagation_result = propagate(ctx.borrow());
let new_unit = prove_units(ctx.borrow());
propagation_result?;
if new_unit {
unit_simplify(ctx.borrow());
}
match enqueue_assumption(ctx.borrow()) {
EnqueueAssumption::Enqueued => continue,
EnqueueAssumption::Conflict => return Err(FoundConflict::Assumption),
EnqueueAssumption::Done => (),
}
if !make_decision(ctx.borrow()) {
return Ok(());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use partial_ref::IntoPartialRefMut;
use varisat_formula::{
cnf_formula,
test::{sat_formula, sgen_unsat_formula},
};
use crate::{load::load_clause, state::SatState};
#[test]
fn level_0_unsat() {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
let formula = cnf_formula![
1, 2, 3;
-1;
1, -2;
2, -3;
];
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
}
while ctx.part(SolverStateP).sat_state == SatState::Unknown {
conflict_step(ctx.borrow());
}
assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unsat);
}
proptest! {
#[test]
fn sgen_unsat(formula in sgen_unsat_formula(1..7usize)) {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
}
while ctx.part(SolverStateP).sat_state == SatState::Unknown {
conflict_step(ctx.borrow());
}
prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unsat);
}
#[test]
fn sat(formula in sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0)) {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
}
while ctx.part(SolverStateP).sat_state == SatState::Unknown {
conflict_step(ctx.borrow());
}
prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Sat);
for clause in formula.iter() {
prop_assert!(clause.iter().any(|&lit| ctx.part(ModelP).lit_is_true(
lit.map_var(|user_var| ctx
.part(VariablesP)
.global_from_user()
.get(user_var)
.expect("no existing global var for user var"))
)));
}
}
#[test]
fn sgen_unsat_incremetal_clauses(formula in sgen_unsat_formula(1..7usize)) {
let mut ctx = Context::default();
let mut ctx = ctx.into_partial_ref_mut();
let mut last_state = SatState::Sat;
for clause in formula.iter() {
load_clause(ctx.borrow(), clause);
while ctx.part(SolverStateP).sat_state == SatState::Unknown {
conflict_step(ctx.borrow());
}
if ctx.part(SolverStateP).sat_state != last_state {
prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unsat);
prop_assert_eq!(last_state, SatState::Sat);
last_state = ctx.part(SolverStateP).sat_state;
}
}
prop_assert_eq!(last_state, SatState::Unsat);
}
}
}