summaryrefslogtreecommitdiffstats
path: root/vendor/varisat/src/assumptions.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat/src/assumptions.rs')
-rw-r--r--vendor/varisat/src/assumptions.rs376
1 files changed, 376 insertions, 0 deletions
diff --git a/vendor/varisat/src/assumptions.rs b/vendor/varisat/src/assumptions.rs
new file mode 100644
index 000000000..c21d8cb7b
--- /dev/null
+++ b/vendor/varisat/src/assumptions.rs
@@ -0,0 +1,376 @@
+//! Incremental solving.
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+use varisat_internal_proof::{clause_hash, lit_hash, ClauseHash, ProofStep};
+
+use crate::{
+ context::{parts::*, Context},
+ proof,
+ prop::{enqueue_assignment, full_restart, Reason},
+ state::SatState,
+ variables,
+};
+
+/// Incremental solving.
+#[derive(Default)]
+pub struct Assumptions {
+ assumptions: Vec<Lit>,
+ failed_core: Vec<Lit>,
+ user_failed_core: Vec<Lit>,
+ assumption_levels: usize,
+ failed_propagation_hashes: Vec<ClauseHash>,
+}
+
+impl Assumptions {
+ /// Current number of decision levels used for assumptions.
+ pub fn assumption_levels(&self) -> usize {
+ self.assumption_levels
+ }
+
+ /// Resets assumption_levels to zero on a full restart.
+ pub fn full_restart(&mut self) {
+ self.assumption_levels = 0;
+ }
+
+ /// Subset of assumptions that made the formula unsatisfiable.
+ pub fn failed_core(&self) -> &[Lit] {
+ &self.failed_core
+ }
+
+ /// Subset of assumptions that made the formula unsatisfiable.
+ pub fn user_failed_core(&self) -> &[Lit] {
+ &self.user_failed_core
+ }
+
+ /// Current assumptions.
+ pub fn assumptions(&self) -> &[Lit] {
+ &self.assumptions
+ }
+}
+
+/// Return type of [`enqueue_assumption`].
+pub enum EnqueueAssumption {
+ Done,
+ Enqueued,
+ Conflict,
+}
+
+/// Change the currently active assumptions.
+///
+/// The input uses user variable names.
+pub fn set_assumptions<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut AssumptionsP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut TrailP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ user_assumptions: &[Lit],
+) {
+ full_restart(ctx.borrow());
+
+ let state = ctx.part_mut(SolverStateP);
+
+ state.sat_state = match state.sat_state {
+ SatState::Unsat => SatState::Unsat,
+ SatState::Sat | SatState::UnsatUnderAssumptions | SatState::Unknown => SatState::Unknown,
+ };
+
+ let (assumptions, mut ctx_2) = ctx.split_part_mut(AssumptionsP);
+
+ for lit in assumptions.assumptions.iter() {
+ ctx_2
+ .part_mut(VariablesP)
+ .var_data_solver_mut(lit.var())
+ .assumed = false;
+ }
+
+ variables::solver_from_user_lits(
+ ctx_2.borrow(),
+ &mut assumptions.assumptions,
+ user_assumptions,
+ true,
+ );
+
+ for lit in assumptions.assumptions.iter() {
+ ctx_2
+ .part_mut(VariablesP)
+ .var_data_solver_mut(lit.var())
+ .assumed = true;
+ }
+
+ proof::add_step(
+ ctx_2.borrow(),
+ true,
+ &ProofStep::Assumptions {
+ assumptions: &assumptions.assumptions,
+ },
+ );
+}
+
+/// Enqueue another assumption if possible.
+///
+/// Returns whether an assumption was enqueued, whether no assumptions are left or whether the
+/// assumptions result in a conflict.
+pub fn enqueue_assumption<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AssignmentP,
+ mut AssumptionsP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut TrailP,
+ ClauseAllocP,
+ VariablesP,
+ ),
+) -> EnqueueAssumption {
+ while let Some(&assumption) = ctx
+ .part(AssumptionsP)
+ .assumptions
+ .get(ctx.part(TrailP).current_level())
+ {
+ match ctx.part(AssignmentP).lit_value(assumption) {
+ Some(false) => {
+ analyze_assumption_conflict(ctx.borrow(), assumption);
+ return EnqueueAssumption::Conflict;
+ }
+ Some(true) => {
+ // The next assumption is already implied by other assumptions so we can remove it.
+ let level = ctx.part(TrailP).current_level();
+ let assumptions = ctx.part_mut(AssumptionsP);
+ assumptions.assumptions.swap_remove(level);
+ }
+ None => {
+ ctx.part_mut(TrailP).new_decision_level();
+ enqueue_assignment(ctx.borrow(), assumption, Reason::Unit);
+ let (assumptions, ctx) = ctx.split_part_mut(AssumptionsP);
+ assumptions.assumption_levels = ctx.part(TrailP).current_level();
+ return EnqueueAssumption::Enqueued;
+ }
+ }
+ }
+ EnqueueAssumption::Done
+}
+
+/// Analyze a conflicting set of assumptions.
+///
+/// Compute a set of incompatible assumptions given an assumption that is incompatible with the
+/// assumptions enqueued so far.
+fn analyze_assumption_conflict<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AssumptionsP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ ClauseAllocP,
+ ImplGraphP,
+ TrailP,
+ VariablesP,
+ ),
+ assumption: Lit,
+) {
+ let (assumptions, mut ctx) = ctx.split_part_mut(AssumptionsP);
+ let (tmp, mut ctx) = ctx.split_part_mut(TmpFlagsP);
+ let (trail, mut ctx) = ctx.split_part(TrailP);
+ let (impl_graph, mut ctx) = ctx.split_part(ImplGraphP);
+
+ let flags = &mut tmp.flags;
+
+ assumptions.failed_core.clear();
+ assumptions.failed_core.push(assumption);
+
+ assumptions.failed_propagation_hashes.clear();
+
+ flags[assumption.index()] = true;
+ let mut flag_count = 1;
+
+ for &lit in trail.trail().iter().rev() {
+ if flags[lit.index()] {
+ flags[lit.index()] = false;
+ flag_count -= 1;
+
+ match impl_graph.reason(lit.var()) {
+ Reason::Unit => {
+ if impl_graph.level(lit.var()) > 0 {
+ assumptions.failed_core.push(lit);
+ }
+ }
+ reason => {
+ let (ctx_lits, ctx) = ctx.split_borrow();
+ let reason_lits = reason.lits(&ctx_lits);
+
+ if ctx.part(ProofP).clause_hashes_required() {
+ let hash = clause_hash(reason_lits) ^ lit_hash(lit);
+ assumptions.failed_propagation_hashes.push(hash);
+ }
+
+ for &reason_lit in reason_lits {
+ if !flags[reason_lit.index()] {
+ flags[reason_lit.index()] = true;
+ flag_count += 1;
+ }
+ }
+ }
+ }
+
+ if flag_count == 0 {
+ break;
+ }
+ }
+ }
+
+ assumptions.failed_propagation_hashes.reverse();
+
+ assumptions.user_failed_core.clear();
+ assumptions
+ .user_failed_core
+ .extend(assumptions.failed_core.iter().map(|solver_lit| {
+ solver_lit
+ .map_var(|solver_var| ctx.part(VariablesP).existing_user_from_solver(solver_var))
+ }));
+
+ proof::add_step(
+ ctx.borrow(),
+ true,
+ &ProofStep::FailedAssumptions {
+ failed_core: &assumptions.failed_core,
+ propagation_hashes: &assumptions.failed_propagation_hashes,
+ },
+ );
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use proptest::{bool, prelude::*};
+
+ use partial_ref::IntoPartialRefMut;
+
+ use varisat_formula::{test::conditional_pigeon_hole, ExtendFormula, Var};
+
+ use crate::{cdcl::conflict_step, load::load_clause, solver::Solver, state::SatState};
+
+ proptest! {
+ #[test]
+ fn pigeon_hole_unsat_assumption_core_internal(
+ (enable_row, columns, formula) in conditional_pigeon_hole(1..5usize, 1..5usize),
+ chain in bool::ANY,
+ ) {
+ let mut ctx = Context::default();
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ for clause in formula.iter() {
+ load_clause(ctx.borrow(), clause);
+ }
+
+ if chain {
+ for (&a, &b) in enable_row.iter().zip(enable_row.iter().skip(1)) {
+ load_clause(ctx.borrow(), &[!a, b]);
+ }
+ }
+
+ while ctx.part(SolverStateP).sat_state == SatState::Unknown {
+ conflict_step(ctx.borrow());
+ }
+
+ prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Sat);
+
+ set_assumptions(ctx.borrow(), &enable_row);
+
+ prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
+
+ while ctx.part(SolverStateP).sat_state == SatState::Unknown {
+ conflict_step(ctx.borrow());
+ }
+
+ prop_assert_eq!(ctx.part(SolverStateP).sat_state, SatState::UnsatUnderAssumptions);
+
+ let mut candidates = ctx.part(AssumptionsP).user_failed_core().to_owned();
+ let mut core: Vec<Lit> = vec![];
+
+ loop {
+ set_assumptions(ctx.borrow(), &candidates[0..candidates.len() - 1]);
+
+ while ctx.part(SolverStateP).sat_state == SatState::Unknown {
+ conflict_step(ctx.borrow());
+ }
+
+ match ctx.part(SolverStateP).sat_state {
+ SatState::Unknown => unreachable!(),
+ SatState::Unsat => break,
+ SatState::Sat => {
+ let skipped = *candidates.last().unwrap();
+ core.push(skipped);
+ load_clause(ctx.borrow(), &[skipped]);
+ },
+ SatState::UnsatUnderAssumptions => {
+ candidates = ctx.part(AssumptionsP).user_failed_core().to_owned();
+ }
+ }
+ }
+ if chain {
+ prop_assert_eq!(core.len(), 1);
+ } else {
+ prop_assert_eq!(core.len(), columns + 1);
+ }
+ }
+
+ #[test]
+ fn pigeon_hole_unsat_assumption_core_solver(
+ (enable_row, columns, formula) in conditional_pigeon_hole(1..5usize, 1..5usize),
+ ) {
+ let mut solver = Solver::new();
+ solver.add_formula(&formula);
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+
+ let mut assumptions = enable_row;
+
+ assumptions.push(Lit::positive(Var::from_index(formula.var_count() + 10)));
+
+ solver.assume(&assumptions);
+
+ prop_assert_eq!(solver.solve().ok(), Some(false));
+
+
+ let mut candidates = solver.failed_core().unwrap().to_owned();
+ let mut core: Vec<Lit> = vec![];
+
+ while !candidates.is_empty() {
+
+ solver.assume(&candidates[0..candidates.len() - 1]);
+
+ match solver.solve() {
+ Err(_) => unreachable!(),
+ Ok(true) => {
+ let skipped = *candidates.last().unwrap();
+ core.push(skipped);
+
+ solver.add_clause(&[skipped]);
+ solver.hide_var(skipped.var());
+ },
+ Ok(false) => {
+ candidates = solver.failed_core().unwrap().to_owned();
+ }
+ }
+ }
+
+ prop_assert_eq!(core.len(), columns + 1);
+ }
+ }
+}