summaryrefslogtreecommitdiffstats
path: root/vendor/varisat-checker/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat-checker/src/lib.rs')
-rw-r--r--vendor/varisat-checker/src/lib.rs773
1 files changed, 773 insertions, 0 deletions
diff --git a/vendor/varisat-checker/src/lib.rs b/vendor/varisat-checker/src/lib.rs
new file mode 100644
index 000000000..22ff9f8ff
--- /dev/null
+++ b/vendor/varisat-checker/src/lib.rs
@@ -0,0 +1,773 @@
+//! Proof checker for Varisat proofs.
+
+use std::io;
+
+use anyhow::Error;
+use partial_ref::{IntoPartialRefMut, PartialRef};
+use thiserror::Error;
+
+use varisat_dimacs::DimacsParser;
+use varisat_formula::{CnfFormula, Lit};
+
+pub mod internal;
+
+mod clauses;
+mod context;
+mod hash;
+mod processing;
+mod rup;
+mod sorted_lits;
+mod state;
+mod tmp;
+mod transcript;
+mod variables;
+
+pub use processing::{
+ CheckedProofStep, CheckedSamplingMode, CheckedUserVar, CheckerData, ProofProcessor,
+ ResolutionPropagations,
+};
+pub use transcript::{ProofTranscriptProcessor, ProofTranscriptStep};
+
+use clauses::add_clause;
+use context::Context;
+use state::check_proof;
+
+/// Possible errors while checking a varisat proof.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum CheckerError {
+ #[error("step {}: Unexpected end of proof file", step)]
+ ProofIncomplete { step: u64 },
+ #[error("step {}: Error reading proof file: {}", step, cause)]
+ IoError {
+ step: u64,
+ #[source]
+ cause: io::Error,
+ },
+ #[error("step {}: Could not parse proof step: {}", step, cause)]
+ ParseError {
+ step: u64,
+ #[source]
+ cause: Error,
+ },
+ #[error("step {}: Checking proof failed: {}", step, msg)]
+ CheckFailed {
+ step: u64,
+ msg: String,
+ debug_step: String,
+ },
+ #[error("Error in proof processor: {}", cause)]
+ ProofProcessorError {
+ #[source]
+ cause: Error,
+ },
+}
+
+impl CheckerError {
+ /// Generate a CheckFailed error with an empty debug_step
+ fn check_failed(step: u64, msg: String) -> CheckerError {
+ CheckerError::CheckFailed {
+ step,
+ msg,
+ debug_step: String::new(),
+ }
+ }
+}
+
+/// A checker for unsatisfiability proofs in the native varisat format.
+#[derive(Default)]
+pub struct Checker<'a> {
+ ctx: Box<Context<'a>>,
+}
+
+impl<'a> Checker<'a> {
+ /// Create a new checker.
+ pub fn new() -> Checker<'a> {
+ Checker::default()
+ }
+
+ /// Adds a clause to the checker.
+ pub fn add_clause(&mut self, clause: &[Lit]) -> Result<(), CheckerError> {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ add_clause(ctx.borrow(), clause)
+ }
+
+ /// Add a formula to the checker.
+ pub fn add_formula(&mut self, formula: &CnfFormula) -> Result<(), CheckerError> {
+ for clause in formula.iter() {
+ self.add_clause(clause)?;
+ }
+
+ Ok(())
+ }
+
+ /// Reads and adds a formula in DIMACS CNF format.
+ ///
+ /// Using this avoids creating a temporary [`CnfFormula`](varisat_formula::CnfFormula).
+ pub fn add_dimacs_cnf(&mut self, input: impl io::Read) -> Result<(), Error> {
+ let parser = DimacsParser::parse_incremental(input, |parser| {
+ Ok(self.add_formula(&parser.take_formula())?)
+ })?;
+
+ log::info!(
+ "Parsed formula with {} variables and {} clauses",
+ parser.var_count(),
+ parser.clause_count()
+ );
+
+ Ok(())
+ }
+
+ /// Add a [`ProofProcessor`].
+ ///
+ /// This has to be called before loading any clauses or checking any proofs.
+ pub fn add_processor(&mut self, processor: &'a mut dyn ProofProcessor) {
+ self.ctx.processing.processors.push(processor);
+ }
+
+ /// Add a [`ProofTranscriptProcessor`].
+ ///
+ /// This has to be called before loading any clauses or checking any proofs.
+ pub fn add_transcript(&mut self, processor: &'a mut dyn ProofTranscriptProcessor) {
+ self.ctx.processing.transcript_processors.push(processor);
+ }
+
+ /// Checks a proof in the native Varisat format.
+ pub fn check_proof(&mut self, input: impl io::Read) -> Result<(), CheckerError> {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ check_proof(ctx.borrow(), input)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{internal::SelfChecker, *};
+
+ use varisat_internal_proof::{DeleteClauseProof, ProofStep};
+
+ use varisat_formula::{cnf_formula, lits, Var};
+
+ fn expect_check_failed(result: Result<(), CheckerError>, contains: &str) {
+ match result {
+ Err(CheckerError::CheckFailed { ref msg, .. }) if msg.contains(contains) => (),
+ err => panic!("expected {:?} error but got {:?}", contains, err),
+ }
+ }
+
+ #[test]
+ fn conflicting_units() {
+ let mut checker = Checker::new();
+
+ checker
+ .add_formula(&cnf_formula![
+ 1;
+ -1;
+ ])
+ .unwrap();
+
+ assert!(checker.ctx.checker_state.unsat);
+ }
+
+ #[test]
+ fn invalid_delete() {
+ let mut checker = Checker::new();
+
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ -4, 5;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: &lits![-5, 4],
+ proof: DeleteClauseProof::Redundant,
+ }),
+ "unknown clause",
+ );
+ }
+
+ #[test]
+ fn ref_counts() {
+ let mut checker = Checker::new();
+
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ 1, 2, 3;
+ 1;
+ ])
+ .unwrap();
+
+ let lits = &lits![1, 2, 3][..];
+
+ checker
+ .self_check_step(ProofStep::DeleteClause {
+ clause: lits,
+ proof: DeleteClauseProof::Satisfied,
+ })
+ .unwrap();
+
+ checker.add_clause(lits).unwrap();
+
+ checker
+ .self_check_step(ProofStep::DeleteClause {
+ clause: lits,
+ proof: DeleteClauseProof::Satisfied,
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::DeleteClause {
+ clause: lits,
+ proof: DeleteClauseProof::Satisfied,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: lits,
+ proof: DeleteClauseProof::Satisfied,
+ }),
+ "unknown clause",
+ );
+ }
+
+ #[test]
+ fn clause_not_found() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::AtClause {
+ redundant: false,
+ clause: [][..].into(),
+ propagation_hashes: [0][..].into(),
+ }),
+ "no clause found",
+ )
+ }
+
+ #[test]
+ fn clause_check_failed() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::AtClause {
+ redundant: false,
+ clause: [][..].into(),
+ propagation_hashes: [][..].into(),
+ }),
+ "AT check failed",
+ )
+ }
+
+ #[test]
+ fn add_derived_tautology() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::AtClause {
+ redundant: false,
+ clause: &lits![-3, 3],
+ propagation_hashes: &[],
+ }),
+ "tautology",
+ )
+ }
+
+ #[test]
+ fn delete_derived_tautology() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ -3, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: &lits![-3, 3],
+ proof: DeleteClauseProof::Redundant,
+ }),
+ "tautology",
+ )
+ }
+
+ #[test]
+ fn delete_unit_clause() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: &lits![1],
+ proof: DeleteClauseProof::Redundant,
+ }),
+ "delete of unit or empty clause",
+ )
+ }
+
+ #[test]
+ fn delete_clause_not_redundant() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: &lits![1, 2, 3],
+ proof: DeleteClauseProof::Redundant,
+ }),
+ "is irredundant",
+ )
+ }
+
+ #[test]
+ fn delete_clause_not_satisfied() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ -2;
+ 4;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: &lits![1, 2, 3],
+ proof: DeleteClauseProof::Satisfied,
+ }),
+ "not satisfied",
+ )
+ }
+
+ #[test]
+ fn delete_clause_not_simplified() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ -3, 4;
+ ])
+ .unwrap();
+
+ let hashes = [
+ checker.ctx.clause_hasher.clause_hash(&lits![1, 2, 3]),
+ checker.ctx.clause_hasher.clause_hash(&lits![-3, 4]),
+ ];
+
+ checker
+ .self_check_step(ProofStep::AtClause {
+ redundant: false,
+ clause: &lits![1, 2, 4],
+ propagation_hashes: &hashes[..],
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteClause {
+ clause: &lits![1, 2, 3],
+ proof: DeleteClauseProof::Simplified,
+ }),
+ "not subsumed",
+ )
+ }
+
+ #[test]
+ fn model_unit_conflict() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1;
+ 2, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::Model {
+ assignment: &lits![-1, 2, -3],
+ }),
+ "conflicts with unit clause",
+ )
+ }
+
+ #[test]
+ fn model_internal_conflict() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 2, 3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::Model {
+ assignment: &lits![-1, 1, 2, -3],
+ }),
+ "conflicting assignment",
+ )
+ }
+
+ #[test]
+ fn model_clause_unsat() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2, 3;
+ -1, -2, 3;
+ 1, -2, -3;
+ ])
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::Model {
+ assignment: &lits![-1, 2, 3],
+ }),
+ "does not satisfy clause",
+ )
+ }
+ #[test]
+ fn model_conflicts_assumptions() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2;
+ -1, 2;
+ ])
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::Assumptions {
+ assumptions: &lits![-2],
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::Model {
+ assignment: &lits![1, 2],
+ }),
+ "does not contain assumption",
+ )
+ }
+
+ #[test]
+ fn model_misses_assumption() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2;
+ -1, 2;
+ ])
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::Assumptions {
+ assumptions: &lits![-3],
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::Model {
+ assignment: &lits![1, 2],
+ }),
+ "does not contain assumption",
+ )
+ }
+
+ #[test]
+ fn failed_core_with_non_assumed_vars() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2;
+ -1, 2;
+ ])
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::Assumptions {
+ assumptions: &lits![-2],
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::FailedAssumptions {
+ failed_core: &lits![-2, -3],
+ propagation_hashes: &[],
+ }),
+ "contains non-assumed variables",
+ )
+ }
+
+ #[test]
+ fn failed_assumptions_with_missing_propagations() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2;
+ -1, 2;
+ -3, -2;
+ ])
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::Assumptions {
+ assumptions: &lits![3],
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::FailedAssumptions {
+ failed_core: &lits![3],
+ propagation_hashes: &[],
+ }),
+ "AT check failed",
+ )
+ }
+
+ #[test]
+ fn failed_assumptions_with_conflicting_assumptions() {
+ let mut checker = Checker::new();
+ checker
+ .add_formula(&cnf_formula![
+ 1, 2;
+ -1, 2;
+ -3, -2;
+ ])
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::Assumptions {
+ assumptions: &lits![3, -3, 4],
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::FailedAssumptions {
+ failed_core: &lits![3, -3],
+ propagation_hashes: &[],
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn add_clause_to_non_sampling_var() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::ChangeSamplingMode {
+ var: Var::from_dimacs(1),
+ sample: false,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::AddClause {
+ clause: &lits![1, 2, 3],
+ }),
+ "not a sampling variable",
+ )
+ }
+
+ #[test]
+ fn add_clause_to_hidden_var() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: None,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::AddClause {
+ clause: &lits![1, 2, 3],
+ }),
+ "not a sampling variable",
+ )
+ }
+
+ #[test]
+ fn colloding_user_vars() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(2),
+ user: Some(Var::from_dimacs(1)),
+ }),
+ "used for two different variables",
+ )
+ }
+
+ #[test]
+ fn observe_without_setting_mode() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: None,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ }),
+ "still hidden",
+ )
+ }
+
+ #[test]
+ fn hide_hidden_var() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: None,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: None,
+ }),
+ "no user name to remove",
+ )
+ }
+
+ #[test]
+ fn delete_user_var() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteVar {
+ var: Var::from_dimacs(1),
+ }),
+ "corresponds to user variable",
+ )
+ }
+
+ #[test]
+ fn delete_in_use_var() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::AddClause {
+ clause: &lits![1, 2, 3],
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: None,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::DeleteVar {
+ var: Var::from_dimacs(1),
+ }),
+ "still has clauses",
+ )
+ }
+
+ #[test]
+ fn invalid_hidden_to_sample() {
+ let mut checker = Checker::new();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: Some(Var::from_dimacs(1)),
+ })
+ .unwrap();
+
+ checker
+ .self_check_step(ProofStep::UserVarName {
+ global: Var::from_dimacs(1),
+ user: None,
+ })
+ .unwrap();
+
+ expect_check_failed(
+ checker.self_check_step(ProofStep::ChangeSamplingMode {
+ var: Var::from_dimacs(1),
+ sample: true,
+ }),
+ "cannot sample hidden variable",
+ )
+ }
+}