summaryrefslogtreecommitdiffstats
path: root/vendor/varisat-checker/src/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat-checker/src/state.rs')
-rw-r--r--vendor/varisat-checker/src/state.rs645
1 files changed, 645 insertions, 0 deletions
diff --git a/vendor/varisat-checker/src/state.rs b/vendor/varisat-checker/src/state.rs
new file mode 100644
index 000000000..7f3e7478f
--- /dev/null
+++ b/vendor/varisat-checker/src/state.rs
@@ -0,0 +1,645 @@
+//! Checker state and checking of proof steps.
+
+use std::{io, mem::replace};
+
+use partial_ref::{partial, PartialRef};
+use rustc_hash::FxHashSet as HashSet;
+
+use varisat_formula::{Lit, Var};
+use varisat_internal_proof::{binary_format::Parser, ClauseHash, DeleteClauseProof, ProofStep};
+
+use crate::{
+ clauses::{
+ add_clause, delete_clause, store_clause, store_unit_clause, DeleteClauseResult,
+ StoreClauseResult, UnitClause, UnitId,
+ },
+ context::{parts::*, Context},
+ hash::rehash,
+ processing::{
+ process_step, CheckedProofStep, CheckedSamplingMode, CheckedUserVar, ResolutionPropagations,
+ },
+ rup::check_clause_with_hashes,
+ sorted_lits::{copy_canonical, is_subset},
+ variables::{
+ add_user_mapping, ensure_sampling_var, ensure_var, remove_user_mapping, SamplingMode,
+ VarData,
+ },
+ CheckerError,
+};
+
+/// A checker for unsatisfiability proofs in the native varisat format.
+#[derive(Default)]
+pub struct CheckerState {
+ /// Current step number.
+ pub step: u64,
+ /// Whether unsatisfiability was proven.
+ pub unsat: bool,
+ /// Whether an end of proof step was checked.
+ ended: bool,
+ /// Last added irredundant clause id.
+ ///
+ /// Sorted and free of duplicates.
+ previous_irred_clause_id: Option<u64>,
+ /// Last added irredundant clause literals.
+ previous_irred_clause_lits: Vec<Lit>,
+ /// Current assumptions, used to check FailedAssumptions and Model
+ assumptions: Vec<Lit>,
+}
+
+impl CheckerState {
+ /// Check whether a given clause is subsumed by the last added irredundant clause.
+ ///
+ /// `lits` must be sorted and free of duplicates.
+ fn subsumed_by_previous_irred_clause(&self, lits: &[Lit]) -> bool {
+ if self.previous_irred_clause_id.is_none() {
+ return false;
+ }
+ is_subset(&self.previous_irred_clause_lits[..], lits, true)
+ }
+}
+
+/// Check a single proof step
+pub fn check_step<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut CheckerStateP,
+ mut ClauseHasherP,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut RupCheckP,
+ mut TmpDataP,
+ mut VariablesP,
+ ),
+ step: ProofStep,
+) -> Result<(), CheckerError> {
+ let mut result = match step {
+ ProofStep::SolverVarName { global, solver } => {
+ ctx.part_mut(ClauseHasherP)
+ .buffered_solver_var_names
+ .push((global, solver));
+ if solver.is_some() {
+ ctx.part_mut(ClauseHasherP)
+ .rename_in_buffered_solver_var_names = true;
+ }
+ Ok(())
+ }
+ ProofStep::UserVarName { global, user } => {
+ if let Some(user) = user {
+ add_user_mapping(ctx.borrow(), global, user)?;
+ } else {
+ remove_user_mapping(ctx.borrow(), global)?;
+ }
+ Ok(())
+ }
+ ProofStep::DeleteVar { var } => check_delete_var_step(ctx.borrow(), var),
+ ProofStep::ChangeSamplingMode { var, sample } => {
+ check_change_sampling_mode(ctx.borrow(), var, sample)
+ }
+ ProofStep::AddClause { clause } => add_clause(ctx.borrow(), clause),
+ ProofStep::AtClause {
+ redundant,
+ clause,
+ propagation_hashes,
+ } => check_at_clause_step(ctx.borrow(), redundant, clause, propagation_hashes),
+ ProofStep::DeleteClause { clause, proof } => {
+ check_delete_clause_step(ctx.borrow(), clause, proof)
+ }
+ ProofStep::UnitClauses { units } => check_unit_clauses_step(ctx.borrow(), units),
+ ProofStep::ChangeHashBits { bits } => {
+ ctx.part_mut(ClauseHasherP).hash_bits = bits;
+ rehash(ctx.borrow());
+ Ok(())
+ }
+ ProofStep::Model { assignment } => check_model_step(ctx.borrow(), assignment),
+ ProofStep::Assumptions { assumptions } => {
+ for &lit in assumptions.iter() {
+ ensure_sampling_var(ctx.borrow(), lit.var())?;
+ }
+ copy_canonical(&mut ctx.part_mut(CheckerStateP).assumptions, assumptions);
+
+ let (state, mut ctx) = ctx.split_part(CheckerStateP);
+
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::Assumptions {
+ assumptions: &state.assumptions,
+ },
+ )?;
+ Ok(())
+ }
+ ProofStep::FailedAssumptions {
+ failed_core,
+ propagation_hashes,
+ } => check_failed_assumptions_step(ctx.borrow(), failed_core, propagation_hashes),
+ ProofStep::End => {
+ ctx.part_mut(CheckerStateP).ended = true;
+ Ok(())
+ }
+ };
+
+ if let Err(CheckerError::CheckFailed {
+ ref mut debug_step, ..
+ }) = result
+ {
+ *debug_step = format!("{:?}", step)
+ }
+ result
+}
+
+/// Check a DeleteVar step
+fn check_delete_var_step<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut VariablesP,
+ CheckerStateP,
+ ),
+ var: Var,
+) -> Result<(), CheckerError> {
+ ensure_var(ctx.borrow(), var);
+ if let Some(user_var) = ctx.part(VariablesP).var_data[var.index()].user_var {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!(
+ "deleted variable {:?} corresponds to user variable {:?}",
+ var, user_var
+ ),
+ ));
+ }
+
+ for &polarity in &[false, true] {
+ if ctx.part(VariablesP).lit_data[var.lit(polarity).code()].clause_count > 0 {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("deleted variable {:?} still has clauses", var),
+ ));
+ }
+ }
+
+ if let Some(unit_clause) = ctx.part(ClausesP).unit_clauses[var.index()] {
+ let clause = [var.lit(unit_clause.value)];
+
+ let id = match unit_clause.id {
+ UnitId::Global(id) => id,
+ _ => unreachable!(),
+ };
+
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::DeleteRatClause {
+ id,
+ keep_as_redundant: false,
+ clause: &clause[..],
+ pivot: clause[0],
+ propagations: &ResolutionPropagations {},
+ },
+ )?;
+ ctx.part_mut(ClausesP).unit_clauses[var.index()] = None;
+ }
+
+ ctx.part_mut(VariablesP).var_data[var.index()] = VarData::default();
+
+ Ok(())
+}
+
+/// Check a ChangeSamplingMode step
+fn check_change_sampling_mode<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut VariablesP,
+ CheckerStateP,
+ ),
+ var: Var,
+ sample: bool,
+) -> Result<(), CheckerError> {
+ ensure_var(ctx.borrow(), var);
+ let mut ctx_in = ctx;
+ let (variables, ctx) = ctx_in.split_part_mut(VariablesP);
+ let var_data = &mut variables.var_data[var.index()];
+ let sampling_mode = if sample {
+ SamplingMode::Sample
+ } else {
+ SamplingMode::Witness
+ };
+
+ if var_data.sampling_mode != sampling_mode {
+ var_data.sampling_mode = sampling_mode;
+
+ if let Some(user_var) = var_data.user_var {
+ let sampling_mode = if var_data.sampling_mode == SamplingMode::Witness {
+ CheckedSamplingMode::Witness
+ } else {
+ CheckedSamplingMode::Sample
+ };
+ process_step(
+ ctx_in.borrow(),
+ &CheckedProofStep::UserVar {
+ var,
+ user_var: Some(CheckedUserVar {
+ user_var,
+ sampling_mode,
+ new_var: false,
+ }),
+ },
+ )?;
+ } else if sampling_mode == SamplingMode::Sample {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("cannot sample hidden variable {:?}", var),
+ ));
+ }
+ }
+
+ Ok(())
+}
+
+/// Check an AtClause step
+fn check_at_clause_step<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut CheckerStateP,
+ mut ClauseHasherP,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut RupCheckP,
+ mut TmpDataP,
+ mut VariablesP,
+ ),
+ redundant: bool,
+ clause: &[Lit],
+ propagation_hashes: &[ClauseHash],
+) -> Result<(), CheckerError> {
+ let mut tmp = replace(&mut ctx.part_mut(TmpDataP).tmp, vec![]);
+
+ if copy_canonical(&mut tmp, clause) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("clause {:?} is a tautology", tmp),
+ ));
+ }
+
+ check_clause_with_hashes(ctx.borrow(), &tmp, &*propagation_hashes)?;
+
+ let (id, added) = store_clause(ctx.borrow(), &tmp, redundant);
+
+ if !redundant {
+ let state = ctx.part_mut(CheckerStateP);
+ state.previous_irred_clause_id = Some(id);
+ state.previous_irred_clause_lits.clear();
+ state.previous_irred_clause_lits.extend_from_slice(&tmp);
+ }
+
+ match added {
+ StoreClauseResult::New => {
+ let (rup_check, mut ctx) = ctx.split_part(RupCheckP);
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::AtClause {
+ id,
+ redundant,
+ clause: &tmp,
+ propagations: &rup_check.trace_ids,
+ },
+ )?;
+ }
+ StoreClauseResult::NewlyIrredundant => {
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::MakeIrredundant { id, clause: &tmp },
+ )?;
+ }
+ StoreClauseResult::Duplicate => (),
+ }
+
+ ctx.part_mut(TmpDataP).tmp = tmp;
+
+ Ok(())
+}
+
+/// Check a DeleteClause step
+fn check_delete_clause_step<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut CheckerStateP,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut TmpDataP,
+ mut VariablesP,
+ ClauseHasherP,
+ ),
+ clause: &[Lit],
+ proof: DeleteClauseProof,
+) -> Result<(), CheckerError> {
+ let mut tmp = replace(&mut ctx.part_mut(TmpDataP).tmp, vec![]);
+
+ if copy_canonical(&mut tmp, clause) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("clause {:?} is a tautology", tmp),
+ ));
+ }
+
+ let redundant = proof == DeleteClauseProof::Redundant;
+
+ let mut subsumed_by = None;
+
+ match proof {
+ DeleteClauseProof::Redundant => (),
+ DeleteClauseProof::Satisfied => {
+ let is_subsumed = tmp.iter().any(|&lit| {
+ if let Some((
+ true,
+ UnitClause {
+ id: UnitId::Global(id),
+ ..
+ },
+ )) = ctx.part(ClausesP).lit_value(lit)
+ {
+ subsumed_by = Some(id);
+ true
+ } else {
+ false
+ }
+ });
+ if !is_subsumed {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("deleted clause {:?} is not satisfied", clause),
+ ));
+ }
+ }
+ DeleteClauseProof::Simplified => {
+ subsumed_by = ctx.part(CheckerStateP).previous_irred_clause_id;
+ if !ctx
+ .part(CheckerStateP)
+ .subsumed_by_previous_irred_clause(&tmp)
+ {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!(
+ "deleted clause {:?} is not subsumed by previous clause {:?}",
+ clause,
+ ctx.part(CheckerStateP).previous_irred_clause_lits
+ ),
+ ));
+ }
+ }
+ }
+
+ ctx.part_mut(CheckerStateP).previous_irred_clause_id = None;
+ ctx.part_mut(CheckerStateP)
+ .previous_irred_clause_lits
+ .clear();
+
+ let (id, deleted) = delete_clause(ctx.borrow(), &tmp, redundant)?;
+
+ if redundant {
+ match deleted {
+ DeleteClauseResult::Removed => {
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::DeleteClause { id, clause: &tmp },
+ )?;
+ }
+ DeleteClauseResult::Unchanged => (),
+ DeleteClauseResult::NewlyRedundant => unreachable!(),
+ }
+ } else {
+ match deleted {
+ DeleteClauseResult::Removed | DeleteClauseResult::NewlyRedundant => {
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::DeleteAtClause {
+ id,
+ keep_as_redundant: deleted == DeleteClauseResult::NewlyRedundant,
+ clause: &tmp,
+ propagations: &[subsumed_by.unwrap()],
+ },
+ )?;
+ }
+ DeleteClauseResult::Unchanged => (),
+ }
+ }
+
+ ctx.part_mut(TmpDataP).tmp = tmp;
+ Ok(())
+}
+
+/// Check a UnitClauses step
+fn check_unit_clauses_step<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut CheckerStateP,
+ mut ClauseHasherP,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut RupCheckP,
+ mut VariablesP,
+ ),
+ units: &[(Lit, ClauseHash)],
+) -> Result<(), CheckerError> {
+ for &(lit, hash) in units.iter() {
+ ensure_var(ctx.borrow(), lit.var());
+
+ let clause = [lit];
+ let propagation_hashes = [hash];
+ check_clause_with_hashes(ctx.borrow(), &clause[..], &propagation_hashes[..])?;
+
+ let (id, added) = store_unit_clause(ctx.borrow(), lit);
+
+ match added {
+ StoreClauseResult::New => {
+ let (rup_check, mut ctx) = ctx.split_part(RupCheckP);
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::AtClause {
+ id,
+ redundant: false,
+ clause: &clause,
+ propagations: &rup_check.trace_ids,
+ },
+ )?;
+ }
+ StoreClauseResult::Duplicate => (),
+ StoreClauseResult::NewlyIrredundant => unreachable!(),
+ }
+ }
+ Ok(())
+}
+
+/// Check a Model step
+fn check_model_step<'a>(
+ mut ctx: partial!(Context<'a>, mut ProcessingP<'a>, CheckerStateP, ClausesP, VariablesP),
+ model: &[Lit],
+) -> Result<(), CheckerError> {
+ let mut assignments = HashSet::default();
+
+ for &lit in model.iter() {
+ if let Some((false, _)) = ctx.part(ClausesP).lit_value(lit) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("model assignment conflicts with unit clause {:?}", !lit),
+ ));
+ }
+ if assignments.contains(&!lit) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("model contains conflicting assignment {:?}", !lit),
+ ));
+ }
+ assignments.insert(lit);
+ }
+
+ for &lit in ctx.part(CheckerStateP).assumptions.iter() {
+ if !assignments.contains(&lit) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("model does not contain assumption {:?}", lit),
+ ));
+ }
+ }
+
+ for (_, candidates) in ctx.part(ClausesP).clauses.iter() {
+ for clause in candidates.iter() {
+ let lits = clause.lits.slice(&ctx.part(ClausesP).literal_buffer);
+ if !lits.iter().any(|lit| assignments.contains(&lit)) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ format!("model does not satisfy clause {:?}", lits),
+ ));
+ }
+ }
+ }
+
+ process_step(ctx.borrow(), &CheckedProofStep::Model { assignment: model })?;
+
+ Ok(())
+}
+
+/// Check a FailedAssumptions step
+fn check_failed_assumptions_step<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut ClauseHasherP,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut RupCheckP,
+ mut TmpDataP,
+ mut VariablesP,
+ CheckerStateP,
+ ),
+ failed_core: &[Lit],
+ propagation_hashes: &[ClauseHash],
+) -> Result<(), CheckerError> {
+ let mut tmp = replace(&mut ctx.part_mut(TmpDataP).tmp, vec![]);
+
+ let direct_conflict = copy_canonical(&mut tmp, failed_core);
+
+ if !is_subset(&tmp, &ctx.part(CheckerStateP).assumptions, false) {
+ return Err(CheckerError::check_failed(
+ ctx.part(CheckerStateP).step,
+ "failed core contains non-assumed variables".to_string(),
+ ));
+ }
+
+ if direct_conflict {
+ // we have x and not x among the assumptions, no need to check
+ ctx.part_mut(RupCheckP).trace_ids.clear();
+ } else {
+ // invert the assumptions and check for an AT
+ for lit in tmp.iter_mut() {
+ *lit = !*lit;
+ }
+ check_clause_with_hashes(ctx.borrow(), &tmp, propagation_hashes)?;
+
+ // we undo the inversion to report the correct checked proof step
+ for lit in tmp.iter_mut() {
+ *lit = !*lit;
+ }
+ }
+
+ let (rup_check, mut ctx) = ctx.split_part(RupCheckP);
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::FailedAssumptions {
+ failed_core: &tmp,
+ propagations: &rup_check.trace_ids,
+ },
+ )?;
+
+ ctx.part_mut(TmpDataP).tmp = tmp;
+
+ Ok(())
+}
+
+/// Checks a proof in the native Varisat format.
+pub fn check_proof<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut CheckerStateP,
+ mut ClauseHasherP,
+ mut ClausesP,
+ mut ProcessingP<'a>,
+ mut RupCheckP,
+ mut TmpDataP,
+ mut VariablesP,
+ ),
+ input: impl io::Read,
+) -> Result<(), CheckerError> {
+ let mut buffer = io::BufReader::new(input);
+ let mut parser = Parser::default();
+
+ while !ctx.part(CheckerStateP).ended {
+ ctx.part_mut(CheckerStateP).step += 1;
+
+ let step = ctx.part(CheckerStateP).step;
+
+ if step % 100000 == 0 {
+ log::info!("checking step {}k", step / 1000);
+ }
+
+ match parser.parse_step(&mut buffer) {
+ Ok(step) => check_step(ctx.borrow(), step)?,
+ Err(err) => match err.downcast::<io::Error>() {
+ Ok(io_err) => {
+ if io_err.kind() == io::ErrorKind::UnexpectedEof {
+ return Err(CheckerError::ProofIncomplete { step });
+ } else {
+ return Err(CheckerError::IoError {
+ step,
+ cause: io_err,
+ });
+ }
+ }
+ Err(err) => return Err(CheckerError::ParseError { step, cause: err }),
+ },
+ }
+ }
+
+ process_unit_conflicts(ctx.borrow())
+}
+
+/// Process unit conflicts detected during clause loading.
+pub fn process_unit_conflicts<'a>(
+ mut ctx: partial!(Context<'a>, mut ProcessingP<'a>, ClausesP, VariablesP),
+) -> Result<(), CheckerError> {
+ let (clauses, mut ctx) = ctx.split_part(ClausesP);
+ if let Some(ids) = &clauses.unit_conflict {
+ let clause = &[];
+
+ process_step(
+ ctx.borrow(),
+ &CheckedProofStep::AtClause {
+ id: clauses.next_clause_id,
+ redundant: false,
+ clause,
+ propagations: ids,
+ },
+ )?;
+ }
+
+ Ok(())
+}