summaryrefslogtreecommitdiffstats
path: root/vendor/varisat/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat/src')
-rw-r--r--vendor/varisat/src/analyze_conflict.rs368
-rw-r--r--vendor/varisat/src/assumptions.rs376
-rw-r--r--vendor/varisat/src/binary.rs100
-rw-r--r--vendor/varisat/src/cdcl.rs264
-rw-r--r--vendor/varisat/src/clause.rs67
-rw-r--r--vendor/varisat/src/clause/activity.rs107
-rw-r--r--vendor/varisat/src/clause/alloc.rs262
-rw-r--r--vendor/varisat/src/clause/assess.rs69
-rw-r--r--vendor/varisat/src/clause/db.rs269
-rw-r--r--vendor/varisat/src/clause/gc.rs207
-rw-r--r--vendor/varisat/src/clause/header.rs148
-rw-r--r--vendor/varisat/src/clause/reduce.rs132
-rw-r--r--vendor/varisat/src/config.rs31
-rw-r--r--vendor/varisat/src/context.rs131
-rw-r--r--vendor/varisat/src/decision.rs58
-rw-r--r--vendor/varisat/src/decision/vsids.rs346
-rw-r--r--vendor/varisat/src/glue.rs36
-rw-r--r--vendor/varisat/src/lib.rs46
-rw-r--r--vendor/varisat/src/load.rs259
-rw-r--r--vendor/varisat/src/model.rs87
-rw-r--r--vendor/varisat/src/proof.rs434
-rw-r--r--vendor/varisat/src/proof/drat.rs85
-rw-r--r--vendor/varisat/src/proof/map_step.rs128
-rw-r--r--vendor/varisat/src/prop.rs261
-rw-r--r--vendor/varisat/src/prop/assignment.rs237
-rw-r--r--vendor/varisat/src/prop/binary.rs36
-rw-r--r--vendor/varisat/src/prop/graph.rs137
-rw-r--r--vendor/varisat/src/prop/long.rs164
-rw-r--r--vendor/varisat/src/prop/watch.rs135
-rw-r--r--vendor/varisat/src/schedule.rs101
-rw-r--r--vendor/varisat/src/schedule/luby.rs52
-rw-r--r--vendor/varisat/src/solver.rs512
-rw-r--r--vendor/varisat/src/state.rs42
-rw-r--r--vendor/varisat/src/tmp.rs35
-rw-r--r--vendor/varisat/src/unit_simplify.rs198
-rw-r--r--vendor/varisat/src/variables.rs669
-rw-r--r--vendor/varisat/src/variables/data.rs70
-rw-r--r--vendor/varisat/src/variables/var_map.rs124
38 files changed, 6783 insertions, 0 deletions
diff --git a/vendor/varisat/src/analyze_conflict.rs b/vendor/varisat/src/analyze_conflict.rs
new file mode 100644
index 000000000..16f918782
--- /dev/null
+++ b/vendor/varisat/src/analyze_conflict.rs
@@ -0,0 +1,368 @@
+//! Learns a new clause by analyzing a conflict.
+use std::mem::swap;
+
+use partial_ref::{partial, split_borrow, PartialRef};
+
+use vec_mut_scan::VecMutScan;
+
+use varisat_formula::{lit::LitIdx, Lit, Var};
+use varisat_internal_proof::{clause_hash, lit_hash, ClauseHash};
+
+use crate::{
+ clause::ClauseRef,
+ context::{parts::*, Context},
+ prop::{Conflict, Reason},
+};
+
+/// Temporaries for conflict analysis
+#[derive(Default)]
+pub struct AnalyzeConflict {
+ /// This is the learned clause after analysis finishes.
+ clause: Vec<Lit>,
+ /// Number of literals in the current clause at the current level.
+ current_level_count: usize,
+ /// Variables in the current clause.
+ var_flags: Vec<bool>,
+ /// Entries to clean in `var_flags`.
+ to_clean: Vec<Var>,
+ /// Clauses to bump.
+ involved: Vec<ClauseRef>,
+ /// Hashes of all involved clauses needed to proof the minimized clause.
+ clause_hashes: Vec<ClauseHash>,
+ /// Clause hashes paired with the trail depth of the propagated lit.
+ unordered_clause_hashes: Vec<(LitIdx, ClauseHash)>,
+ /// Stack for recursive minimization.
+ stack: Vec<Lit>,
+}
+
+impl AnalyzeConflict {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.var_flags.resize(count, false);
+ }
+
+ /// The learned clause.
+ pub fn clause(&self) -> &[Lit] {
+ &self.clause
+ }
+
+ /// Long clauses involved in the conflict.
+ pub fn involved(&self) -> &[ClauseRef] {
+ &self.involved
+ }
+
+ /// Hashes of clauses involved in the proof of the learned clause.
+ ///
+ /// Hashes are in clause propagation order.
+ pub fn clause_hashes(&self) -> &[ClauseHash] {
+ &self.clause_hashes
+ }
+}
+
+/// Learns a new clause by analyzing a conflict.
+///
+/// Returns the lowest decision level that makes the learned clause asserting.
+pub fn analyze_conflict<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut VsidsP,
+ ClauseAllocP,
+ ImplGraphP,
+ ProofP<'a>,
+ TrailP,
+ ),
+ conflict: Conflict,
+) -> usize {
+ split_borrow!(lit_ctx = &(ClauseAllocP) ctx);
+
+ {
+ let analyze = ctx.part_mut(AnalyzeConflictP);
+
+ analyze.clause.clear();
+ analyze.involved.clear();
+ analyze.clause_hashes.clear();
+ analyze.unordered_clause_hashes.clear();
+ analyze.current_level_count = 0;
+ }
+
+ // We start with all the literals of the conflicted clause
+ let conflict_lits = conflict.lits(&lit_ctx);
+
+ if ctx.part(ProofP).clause_hashes_required() {
+ ctx.part_mut(AnalyzeConflictP)
+ .clause_hashes
+ .push(clause_hash(conflict_lits));
+ }
+
+ if ctx.part(TrailP).current_level() == 0 {
+ // Conflict with no decisions, generate empty clause
+ return 0;
+ }
+
+ for &lit in conflict_lits {
+ add_literal(ctx.borrow(), lit);
+ }
+
+ if let Conflict::Long(cref) = conflict {
+ ctx.part_mut(AnalyzeConflictP).involved.push(cref);
+ }
+
+ // To get rid of all but one literal of the current level, we resolve the clause with the reason
+ // for those literals. The correct order for this is reverse chronological.
+
+ split_borrow!(ctx_trail = &(TrailP) ctx);
+ // split_borrow!(ctx_analyze = &(mut AnalyzeConflictP) ctx);
+
+ for &lit in ctx_trail.part(TrailP).trail().iter().rev() {
+ let analyze = ctx.part_mut(AnalyzeConflictP);
+ let lit_present = &mut analyze.var_flags[lit.index()];
+ // Is the lit present in the current clause?
+ if *lit_present {
+ *lit_present = false;
+ analyze.current_level_count -= 1;
+ if analyze.current_level_count == 0 {
+ // lit is the last literal of the current level present in the current clause,
+ // therefore the resulting clause will assert !lit so we put in position 0
+ analyze.clause.push(!lit);
+ let end = analyze.clause.len() - 1;
+ analyze.clause.swap(0, end);
+
+ break;
+ } else {
+ // We removed the literal and now add its reason.
+ let (graph, mut ctx) = ctx.split_part(ImplGraphP);
+
+ let reason = graph.reason(lit.var());
+
+ let lits = reason.lits(&lit_ctx);
+
+ if ctx.part(ProofP).clause_hashes_required() && !reason.is_unit() {
+ let hash = clause_hash(lits) ^ lit_hash(lit);
+ ctx.part_mut(AnalyzeConflictP).clause_hashes.push(hash);
+ }
+
+ for &lit in lits {
+ add_literal(ctx.borrow(), lit);
+ }
+
+ if let Reason::Long(cref) = reason {
+ ctx.part_mut(AnalyzeConflictP).involved.push(*cref);
+ }
+ }
+ }
+ }
+
+ // This needs var_flags set and keeps some var_fags set.
+ minimize_clause(ctx.borrow());
+
+ let (analyze, mut ctx) = ctx.split_part_mut(AnalyzeConflictP);
+
+ if ctx.part(ProofP).clause_hashes_required() {
+ // Clause minimization cannot give us clause hashes in propagation order, so we need to sort
+ // them. Clauses used during minimization propagte before clauses used during initial
+ // analysis. The clauses during initial analysis are discovered in reverse propagation
+ // order. This means we can sort the minimization clauses in reverse order, append them to
+ // the initial clauses and then reverse the order of all clauses.
+ analyze
+ .unordered_clause_hashes
+ .sort_unstable_by_key(|&(depth, _)| !depth);
+
+ analyze
+ .unordered_clause_hashes
+ .dedup_by_key(|&mut (depth, _)| depth);
+
+ analyze.clause_hashes.extend(
+ analyze
+ .unordered_clause_hashes
+ .iter()
+ .map(|&(_, hash)| hash),
+ );
+ analyze.clause_hashes.reverse();
+ }
+
+ for var in analyze.to_clean.drain(..) {
+ analyze.var_flags[var.index()] = false;
+ }
+
+ // We find the highest level literal besides the asserted literal and move it into position 1.
+ // This is important to ensure the watchlist constraints are not violated on backtracking.
+ let mut backtrack_to = 0;
+
+ if analyze.clause.len() > 1 {
+ let (prefix, rest) = analyze.clause.split_at_mut(2);
+ let lit_1 = &mut prefix[1];
+ backtrack_to = ctx.part(ImplGraphP).level(lit_1.var());
+ for lit in rest.iter_mut() {
+ let lit_level = ctx.part(ImplGraphP).level(lit.var());
+ if lit_level > backtrack_to {
+ backtrack_to = lit_level;
+ swap(lit_1, lit);
+ }
+ }
+ }
+
+ ctx.part_mut(VsidsP).decay();
+
+ backtrack_to
+}
+
+/// Add a literal to the current clause.
+fn add_literal(
+ mut ctx: partial!(
+ Context,
+ mut AnalyzeConflictP,
+ mut VsidsP,
+ ImplGraphP,
+ TrailP
+ ),
+ lit: Lit,
+) {
+ let (analyze, mut ctx) = ctx.split_part_mut(AnalyzeConflictP);
+ let lit_level = ctx.part(ImplGraphP).level(lit.var());
+ // No need to add literals that are set by unit clauses or already present
+ if lit_level > 0 && !analyze.var_flags[lit.index()] {
+ ctx.part_mut(VsidsP).bump(lit.var());
+
+ analyze.var_flags[lit.index()] = true;
+ if lit_level == ctx.part(TrailP).current_level() {
+ analyze.current_level_count += 1;
+ } else {
+ analyze.clause.push(lit);
+ analyze.to_clean.push(lit.var());
+ }
+ }
+}
+
+/// A Bloom filter of levels.
+#[derive(Default)]
+struct LevelAbstraction {
+ bits: u64,
+}
+
+impl LevelAbstraction {
+ /// Add a level to the Bloom filter.
+ pub fn add(&mut self, level: usize) {
+ self.bits |= 1 << (level % 64)
+ }
+
+ /// Test whether a level could be in the Bloom filter.
+ pub fn test(&self, level: usize) -> bool {
+ self.bits & (1 << (level % 64)) != 0
+ }
+}
+
+/// Performs recursive clause minimization.
+///
+/// **Note:** Requires AnalyzeConflict's var_flags to be set for exactly the variables of the
+/// unminimized claused. This also sets some more var_flags, but lists them in to_clean.
+///
+/// This routine tries to remove some redundant literals of the learned clause. The idea is to
+/// detect literals of the learned clause that are already implied by other literals of the clause.
+///
+/// This is done by performing a DFS in the implication graph (following edges in reverse) for each
+/// literal (apart from the asserting one). The search doesn't expand literals already known to be
+/// implied by literals of the clause. When a decision literal that is not in the clause is found,
+/// it means that the literal is not redundant.
+///
+/// There are two optimizations used here: The first one is to stop the search as soon as a literal
+/// of a decision level not present in the clause is found. If the DFS would be continued it would
+/// at some point reach the decision of that level. That decision belongs to a level not in the
+/// clause and thus itself can't be in the clause. Checking whether the decision level is among the
+/// clause's decision levels is done approximately using a Bloom filter.
+///
+/// The other optimization is to avoid duplicating work during the DFS searches. When one literal is
+/// found to be redundant that means the whole search stayed within the implied literals. We
+/// remember this and will not expand any of these literals for the following DFS searches.
+///
+/// In this implementation the var_flags array here has two purposes. At the beginning it is set for
+/// all the literals of the clause. It is also used to mark the literals visited during the DFS.
+/// This allows us to combine the already-visited-check with the literal-present-in-clause check. It
+/// also allows for a neat implementation of the second optimization. When the search finds the
+/// literal to be non-redundant, we clear var_flags for the literals we visited, resetting it to the
+/// state at the beginning of the DFS. When the literal was redundant we keep it as is. This means
+/// the following DFS will not expand these literals.
+fn minimize_clause<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut VsidsP,
+ ClauseAllocP,
+ ImplGraphP,
+ ProofP<'a>,
+ TrailP,
+ ),
+) {
+ let (analyze, mut ctx) = ctx.split_part_mut(AnalyzeConflictP);
+ split_borrow!(lit_ctx = &(ClauseAllocP) ctx);
+ let impl_graph = ctx.part(ImplGraphP);
+
+ let mut involved_levels = LevelAbstraction::default();
+
+ for &lit in analyze.clause.iter() {
+ involved_levels.add(impl_graph.level(lit.var()));
+ }
+
+ let mut scan = VecMutScan::new(&mut analyze.clause);
+
+ // we always keep the first literal
+ scan.next();
+
+ 'next_lit: while let Some(lit) = scan.next() {
+ if impl_graph.reason(lit.var()) == &Reason::Unit {
+ continue;
+ }
+
+ // Start the DFS
+ analyze.stack.clear();
+ analyze.stack.push(!*lit);
+
+ // Used to remember which var_flags are set during this DFS
+ let top = analyze.to_clean.len();
+
+ // Used to remember which clause hashes were added during the DFS, so we can remove them in
+ // case the literal is not redundant.
+ let hashes_top = analyze.unordered_clause_hashes.len();
+
+ while let Some(lit) = analyze.stack.pop() {
+ let reason = impl_graph.reason(lit.var());
+ let lits = reason.lits(&lit_ctx);
+
+ if ctx.part(ProofP).clause_hashes_required() && !reason.is_unit() {
+ let depth = impl_graph.depth(lit.var()) as LitIdx;
+ let hash = clause_hash(lits) ^ lit_hash(lit);
+ analyze.unordered_clause_hashes.push((depth, hash));
+ }
+
+ for &reason_lit in lits {
+ let reason_level = impl_graph.level(reason_lit.var());
+
+ if !analyze.var_flags[reason_lit.index()] && reason_level > 0 {
+ // We haven't established reason_lit to be redundant, haven't visited it yet and
+ // it's not implied by unit clauses.
+
+ if impl_graph.reason(reason_lit.var()) == &Reason::Unit
+ || !involved_levels.test(reason_level)
+ {
+ // reason_lit is a decision not in the clause or in a decision level known
+ // not to be in the clause. Abort the search.
+
+ // Reset the var_flags set during _this_ DFS.
+ for lit in analyze.to_clean.drain(top..) {
+ analyze.var_flags[lit.index()] = false;
+ }
+ // Remove clauses not needed to justify the minimized clause.
+ analyze.unordered_clause_hashes.truncate(hashes_top);
+ continue 'next_lit;
+ } else {
+ analyze.var_flags[reason_lit.index()] = true;
+ analyze.to_clean.push(reason_lit.var());
+ analyze.stack.push(!reason_lit);
+ }
+ }
+ }
+ }
+
+ lit.remove();
+ }
+}
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);
+ }
+ }
+}
diff --git a/vendor/varisat/src/binary.rs b/vendor/varisat/src/binary.rs
new file mode 100644
index 000000000..f39d04676
--- /dev/null
+++ b/vendor/varisat/src/binary.rs
@@ -0,0 +1,100 @@
+//! Binary clauses.
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+use varisat_internal_proof::{DeleteClauseProof, ProofStep};
+
+use crate::{
+ context::{parts::*, Context},
+ proof,
+};
+
+/// Binary clauses.
+#[derive(Default)]
+pub struct BinaryClauses {
+ by_lit: Vec<Vec<Lit>>,
+ count: usize,
+}
+
+impl BinaryClauses {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.by_lit.resize(count * 2, vec![]);
+ }
+
+ /// Add a binary clause.
+ pub fn add_binary_clause(&mut self, lits: [Lit; 2]) {
+ for i in 0..2 {
+ self.by_lit[(!lits[i]).code()].push(lits[i ^ 1]);
+ }
+ self.count += 1;
+ }
+
+ /// Implications of a given literal
+ pub fn implied(&self, lit: Lit) -> &[Lit] {
+ &self.by_lit[lit.code()]
+ }
+
+ /// Number of binary clauses.
+ pub fn count(&self) -> usize {
+ self.count
+ }
+}
+
+/// Remove binary clauses that have an assigned literal.
+pub fn simplify_binary<'a>(
+ mut ctx: partial!(Context<'a>, mut BinaryClausesP, mut ProofP<'a>, mut SolverStateP, AssignmentP, VariablesP),
+) {
+ let (binary_clauses, mut ctx) = ctx.split_part_mut(BinaryClausesP);
+ let (assignment, mut ctx) = ctx.split_part(AssignmentP);
+
+ let mut double_count = 0;
+
+ for (code, implied) in binary_clauses.by_lit.iter_mut().enumerate() {
+ let lit = Lit::from_code(code);
+
+ if !assignment.lit_is_unk(lit) {
+ if ctx.part(ProofP).is_active() {
+ for &other_lit in implied.iter() {
+ // This check avoids deleting binary clauses twice if both literals are assigned.
+ if (!lit) < other_lit {
+ let lits = [!lit, other_lit];
+ proof::add_step(
+ ctx.borrow(),
+ true,
+ &ProofStep::DeleteClause {
+ clause: &lits[..],
+ proof: DeleteClauseProof::Satisfied,
+ },
+ );
+ }
+ }
+ }
+
+ implied.clear();
+ } else {
+ implied.retain(|&other_lit| {
+ let retain = assignment.lit_is_unk(other_lit);
+ // This check avoids deleting binary clauses twice if both literals are assigned.
+ if ctx.part(ProofP).is_active() && !retain && (!lit) < other_lit {
+ let lits = [!lit, other_lit];
+ proof::add_step(
+ ctx.borrow(),
+ true,
+ &ProofStep::DeleteClause {
+ clause: &lits[..],
+ proof: DeleteClauseProof::Satisfied,
+ },
+ );
+ }
+
+ retain
+ });
+
+ double_count += implied.len();
+ }
+ }
+
+ binary_clauses.count = double_count / 2;
+}
diff --git a/vendor/varisat/src/cdcl.rs b/vendor/varisat/src/cdcl.rs
new file mode 100644
index 000000000..88ee76d6a
--- /dev/null
+++ b/vendor/varisat/src/cdcl.rs
@@ -0,0 +1,264 @@
+//! 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);
+ }
+ }
+}
diff --git a/vendor/varisat/src/clause.rs b/vendor/varisat/src/clause.rs
new file mode 100644
index 000000000..0d3591858
--- /dev/null
+++ b/vendor/varisat/src/clause.rs
@@ -0,0 +1,67 @@
+//! Clause storage.
+use std::slice;
+
+use varisat_formula::{lit::LitIdx, Lit};
+
+pub mod activity;
+pub mod alloc;
+pub mod assess;
+pub mod db;
+pub mod gc;
+pub mod header;
+pub mod reduce;
+
+pub use activity::{bump_clause_activity, decay_clause_activities, ClauseActivity};
+pub use alloc::{ClauseAlloc, ClauseRef};
+pub use assess::{assess_learned_clause, bump_clause};
+pub use db::{ClauseDb, Tier};
+pub use gc::collect_garbage;
+pub use header::ClauseHeader;
+
+use header::HEADER_LEN;
+
+/// A clause.
+///
+/// This is stoed in a [`ClauseAlloc`] and thus must have a representation compatible with slice of
+/// [`LitIdx`] values.
+///
+/// It would be nicer to use a DST struct with two members and `repr(C)`, but while that can be
+/// declared in stable rust, it's almost impossible to work with.
+#[repr(transparent)]
+pub struct Clause {
+ data: [LitIdx],
+}
+
+impl Clause {
+ /// The clause's header
+ pub fn header(&self) -> &ClauseHeader {
+ unsafe {
+ let header_ptr = self.data.as_ptr() as *const ClauseHeader;
+ &*header_ptr
+ }
+ }
+
+ /// Mutable reference to the clause's header
+ pub fn header_mut(&mut self) -> &mut ClauseHeader {
+ unsafe {
+ let header_ptr = self.data.as_mut_ptr() as *mut ClauseHeader;
+ &mut *header_ptr
+ }
+ }
+
+ /// The clause's literals
+ pub fn lits(&self) -> &[Lit] {
+ unsafe {
+ let lit_ptr = self.data.as_ptr().add(HEADER_LEN) as *const Lit;
+ slice::from_raw_parts(lit_ptr, self.data.len() - HEADER_LEN)
+ }
+ }
+
+ /// Mutable slice of the clause's literals
+ pub fn lits_mut(&mut self) -> &mut [Lit] {
+ unsafe {
+ let lit_ptr = self.data.as_mut_ptr().add(HEADER_LEN) as *mut Lit;
+ slice::from_raw_parts_mut(lit_ptr, self.data.len() - HEADER_LEN)
+ }
+ }
+}
diff --git a/vendor/varisat/src/clause/activity.rs b/vendor/varisat/src/clause/activity.rs
new file mode 100644
index 000000000..8404c7e75
--- /dev/null
+++ b/vendor/varisat/src/clause/activity.rs
@@ -0,0 +1,107 @@
+//! Clause activity.
+use partial_ref::{partial, PartialRef};
+
+use crate::{
+ config::SolverConfig,
+ context::{parts::*, Context},
+};
+
+use super::ClauseRef;
+
+/// Clause activity.
+///
+/// The individual clause activities are stored in the clause allocator. This stores global metadata
+/// used for bumping and decaying activities.
+pub struct ClauseActivity {
+ /// The value to add on bumping.
+ bump: f32,
+ /// The inverse of the decay factor.
+ inv_decay: f32,
+}
+
+impl Default for ClauseActivity {
+ fn default() -> ClauseActivity {
+ ClauseActivity {
+ bump: 1.0,
+ inv_decay: 1.0 / SolverConfig::default().clause_activity_decay,
+ }
+ }
+}
+
+impl ClauseActivity {
+ /// Change the decay factor.
+ pub fn set_decay(&mut self, decay: f32) {
+ assert!(decay < 1.0);
+ assert!(decay > 1.0 / 16.0);
+ self.inv_decay = 1.0 / decay;
+ }
+}
+
+/// Rescale activities if any value exceeds this value.
+fn rescale_limit() -> f32 {
+ std::f32::MAX / 16.0
+}
+
+/// Increase a clause's activity.
+pub fn bump_clause_activity(
+ mut ctx: partial!(
+ Context,
+ mut ClauseActivityP,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ ),
+ cref: ClauseRef,
+) {
+ let bump = ctx.part(ClauseActivityP).bump;
+ let header = ctx.part_mut(ClauseAllocP).header_mut(cref);
+
+ let activity = header.activity() + bump;
+
+ header.set_activity(activity);
+
+ if activity > rescale_limit() {
+ rescale_clause_activities(ctx.borrow());
+ }
+}
+
+/// Rescale all values to avoid an overflow.
+fn rescale_clause_activities(
+ mut ctx: partial!(
+ Context,
+ mut ClauseActivityP,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ ),
+) {
+ let rescale_factor = 1.0 / rescale_limit();
+
+ let (db, mut ctx) = ctx.split_part_mut(ClauseDbP);
+ let (alloc, mut ctx) = ctx.split_part_mut(ClauseAllocP);
+ db.clauses.retain(|&cref| {
+ let header = alloc.header_mut(cref);
+ if header.deleted() {
+ false
+ } else {
+ let activity = header.activity() * rescale_factor;
+ header.set_activity(activity);
+ true
+ }
+ });
+ ctx.part_mut(ClauseActivityP).bump *= rescale_factor;
+}
+
+/// Decay the clause activities.
+pub fn decay_clause_activities(
+ mut ctx: partial!(
+ Context,
+ mut ClauseActivityP,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ ),
+) {
+ let activities = ctx.part_mut(ClauseActivityP);
+ activities.bump *= activities.inv_decay;
+ if activities.bump >= rescale_limit() {
+ rescale_clause_activities(ctx.borrow());
+ }
+}
diff --git a/vendor/varisat/src/clause/alloc.rs b/vendor/varisat/src/clause/alloc.rs
new file mode 100644
index 000000000..d39514040
--- /dev/null
+++ b/vendor/varisat/src/clause/alloc.rs
@@ -0,0 +1,262 @@
+//! Clause allocator.
+use std::{mem::transmute, slice};
+
+use varisat_formula::{lit::LitIdx, Lit};
+
+use super::{Clause, ClauseHeader, HEADER_LEN};
+
+/// Integer type used to store offsets into [`ClauseAlloc`]'s memory.
+type ClauseOffset = u32;
+
+/// Bump allocator for clause storage.
+///
+/// Clauses are allocated from a single continuous buffer. Clauses cannot be freed individually. To
+/// reclaim space from deleted clauses, a new `ClauseAlloc` is created and the remaining clauses are
+/// copied over.
+///
+/// When the `ClauseAlloc`'s buffer is full, it is reallocated using the growing strategy of
+/// [`Vec`]. External references ([`ClauseRef`]) store an offset into the `ClauseAlloc`'s memory and
+/// remaind valid when the buffer is grown. Clauses are aligned and the offset represents a multiple
+/// of the alignment size. This allows using 32-bit offsets while still supporting up to 16GB of
+/// clauses.
+#[derive(Default)]
+pub struct ClauseAlloc {
+ buffer: Vec<LitIdx>,
+}
+
+impl ClauseAlloc {
+ /// Create an emtpy clause allocator.
+ pub fn new() -> ClauseAlloc {
+ ClauseAlloc::default()
+ }
+
+ /// Create a clause allocator with preallocated capacity.
+ pub fn with_capacity(capacity: usize) -> ClauseAlloc {
+ ClauseAlloc {
+ buffer: Vec::with_capacity(capacity),
+ }
+ }
+
+ /// Allocate space for and add a new clause.
+ ///
+ /// Clauses have a minimal size of 3, as binary and unit clauses are handled separately. This is
+ /// enforced on the ClauseAlloc level to safely avoid extra bound checks when accessing the
+ /// initial literals of a clause.
+ ///
+ /// The size of the header will be set to the size of the given slice. The returned
+ /// [`ClauseRef`] can be used to access the new clause.
+ pub fn add_clause(&mut self, mut header: ClauseHeader, lits: &[Lit]) -> ClauseRef {
+ let offset = self.buffer.len();
+
+ assert!(
+ lits.len() >= 3,
+ "ClauseAlloc can only store ternary and larger clauses"
+ );
+
+ // TODO Maybe let the caller handle this?
+ assert!(
+ offset <= (ClauseRef::max_offset() as usize),
+ "Exceeded ClauseAlloc's maximal buffer size"
+ );
+
+ header.set_len(lits.len());
+
+ self.buffer.extend_from_slice(&header.data);
+
+ let lit_idx_slice = unsafe {
+ // This is safe as Lit and LitIdx have the same representation
+ slice::from_raw_parts(lits.as_ptr() as *const LitIdx, lits.len())
+ };
+
+ self.buffer.extend_from_slice(lit_idx_slice);
+
+ ClauseRef {
+ offset: offset as ClauseOffset,
+ }
+ }
+
+ /// Access the header of a clause.
+ pub fn header(&self, cref: ClauseRef) -> &ClauseHeader {
+ let offset = cref.offset as usize;
+ assert!(
+ offset as usize + HEADER_LEN <= self.buffer.len(),
+ "ClauseRef out of bounds"
+ );
+ unsafe { self.header_unchecked(cref) }
+ }
+
+ /// Mutate the header of a clause.
+ pub fn header_mut(&mut self, cref: ClauseRef) -> &mut ClauseHeader {
+ let offset = cref.offset as usize;
+ assert!(
+ offset as usize + HEADER_LEN <= self.buffer.len(),
+ "ClauseRef out of bounds"
+ );
+ unsafe { self.header_unchecked_mut(cref) }
+ }
+
+ unsafe fn header_unchecked(&self, cref: ClauseRef) -> &ClauseHeader {
+ let offset = cref.offset as usize;
+ let header_pointer = self.buffer.as_ptr().add(offset) as *const ClauseHeader;
+ &*header_pointer
+ }
+
+ /// Mutate the header of a clause without bound checks.
+ pub unsafe fn header_unchecked_mut(&mut self, cref: ClauseRef) -> &mut ClauseHeader {
+ let offset = cref.offset as usize;
+ let header_pointer = self.buffer.as_mut_ptr().add(offset) as *mut ClauseHeader;
+ &mut *header_pointer
+ }
+
+ /// Access a clause.
+ pub fn clause(&self, cref: ClauseRef) -> &Clause {
+ let header = self.header(cref);
+ let len = header.len();
+
+ // Even on 32 bit systems these additions can't overflow as we never create clause refs with
+ // an offset larger than ClauseRef::max_offset()
+
+ let lit_offset = cref.offset as usize + HEADER_LEN;
+ let lit_end = lit_offset + len;
+ assert!(lit_end <= self.buffer.len(), "ClauseRef out of bounds");
+ unsafe { self.clause_with_len_unchecked(cref, len) }
+ }
+
+ /// Mutate a clause.
+ pub fn clause_mut(&mut self, cref: ClauseRef) -> &mut Clause {
+ let header = self.header(cref);
+ let len = header.len();
+
+ // Even on 32 bit systems these additions can't overflow as we never create clause refs with
+ // an offset larger than ClauseRef::max_offset()
+
+ let lit_offset = cref.offset as usize + HEADER_LEN;
+ let lit_end = lit_offset + len;
+ assert!(lit_end <= self.buffer.len(), "ClauseRef out of bounds");
+ unsafe { self.clause_with_len_unchecked_mut(cref, len) }
+ }
+
+ /// Mutate the literals of a clause without bound checks.
+ pub unsafe fn lits_ptr_mut_unchecked(&mut self, cref: ClauseRef) -> *mut Lit {
+ let offset = cref.offset as usize;
+ self.buffer.as_ptr().add(offset + HEADER_LEN) as *mut Lit
+ }
+
+ /// Perform a manual bound check on a ClauseRef assuming a given clause length.
+ pub fn check_bounds(&self, cref: ClauseRef, len: usize) {
+ // Even on 32 bit systems these additions can't overflow as we never create clause refs with
+ // an offset larger than ClauseRef::max_offset()
+
+ let lit_offset = cref.offset as usize + HEADER_LEN;
+ let lit_end = lit_offset + len;
+ assert!(lit_end <= self.buffer.len(), "ClauseRef out of bounds");
+ }
+
+ unsafe fn clause_with_len_unchecked(&self, cref: ClauseRef, len: usize) -> &Clause {
+ let offset = cref.offset as usize;
+ #[allow(clippy::transmute_ptr_to_ptr)]
+ transmute::<&[LitIdx], &Clause>(slice::from_raw_parts(
+ self.buffer.as_ptr().add(offset),
+ len + HEADER_LEN,
+ ))
+ }
+
+ unsafe fn clause_with_len_unchecked_mut(&mut self, cref: ClauseRef, len: usize) -> &mut Clause {
+ let offset = cref.offset as usize;
+ #[allow(clippy::transmute_ptr_to_ptr)]
+ transmute::<&mut [LitIdx], &mut Clause>(slice::from_raw_parts_mut(
+ self.buffer.as_mut_ptr().add(offset),
+ len + HEADER_LEN,
+ ))
+ }
+
+ /// Current buffer size in multiples of [`LitIdx`].
+ pub fn buffer_size(&self) -> usize {
+ self.buffer.len()
+ }
+}
+
+/// Compact reference to a clause.
+///
+/// Used with [`ClauseAlloc`] to access the clause.
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
+pub struct ClauseRef {
+ offset: ClauseOffset,
+}
+
+impl ClauseRef {
+ /// The largest offset supported by the ClauseAlloc
+ const fn max_offset() -> ClauseOffset {
+ // Make sure we can savely add a length to an offset without overflowing usize
+ ((usize::max_value() >> 1) & (ClauseOffset::max_value() as usize)) as ClauseOffset
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use varisat_formula::{cnf::strategy::*, CnfFormula, ExtendFormula};
+
+ use proptest::*;
+
+ proptest! {
+ #[test]
+ fn roundtrip_from_cnf_formula(input in cnf_formula(1..100usize, 0..1000, 3..30)) {
+
+ let mut clause_alloc = ClauseAlloc::new();
+ let mut clause_refs = vec![];
+
+ for clause_lits in input.iter() {
+ let header = ClauseHeader::new();
+ clause_refs.push(clause_alloc.add_clause(header, clause_lits));
+ }
+
+ let mut recovered = CnfFormula::new();
+
+ for cref in clause_refs {
+ let clause = clause_alloc.clause(cref);
+ prop_assert_eq!(clause.header().len(), clause.lits().len());
+ recovered.add_clause(clause.lits());
+ }
+
+ // Ignore difference caused by unused vars
+ recovered.set_var_count(input.var_count());
+
+ prop_assert_eq!(input, recovered);
+ }
+
+ #[test]
+ fn clause_mutation(input in cnf_formula(1..100usize, 0..1000, 3..30)) {
+
+ let mut clause_alloc = ClauseAlloc::new();
+ let mut clause_refs = vec![];
+
+ for clause_lits in input.iter() {
+ let header = ClauseHeader::new();
+ clause_refs.push(clause_alloc.add_clause(header, clause_lits));
+ }
+
+ for &cref in clause_refs.iter() {
+ let clause = clause_alloc.clause_mut(cref);
+ clause.lits_mut().reverse();
+ }
+
+ for &cref in clause_refs.iter() {
+ let clause_len = clause_alloc.clause(cref).lits().len();
+ if clause_len > 3 {
+ clause_alloc.header_mut(cref).set_len(clause_len - 1);
+ }
+ }
+
+ for (&cref, lits) in clause_refs.iter().zip(input.iter()) {
+ let expected = if lits.len() > 3 {
+ lits[1..].iter().rev()
+ } else {
+ lits.iter().rev()
+ };
+ prop_assert!(clause_alloc.clause(cref).lits().iter().eq(expected));
+ }
+ }
+ }
+}
diff --git a/vendor/varisat/src/clause/assess.rs b/vendor/varisat/src/clause/assess.rs
new file mode 100644
index 000000000..a28dacb64
--- /dev/null
+++ b/vendor/varisat/src/clause/assess.rs
@@ -0,0 +1,69 @@
+//! Clause assessment.
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+
+use crate::{
+ clause::{db, ClauseRef},
+ context::{parts::*, Context},
+ glue::compute_glue,
+};
+
+use super::{bump_clause_activity, ClauseHeader, Tier};
+
+/// Assess the newly learned clause and generate a clause header.
+pub fn assess_learned_clause(
+ mut ctx: partial!(Context, mut TmpFlagsP, ImplGraphP),
+ lits: &[Lit],
+) -> ClauseHeader {
+ // This is called while the clause is still in conflict, thus the computed glue level is one
+ // higher than it'll be after backtracking when the clause becomes asserting.
+ let glue = compute_glue(ctx.borrow(), lits) - 1;
+
+ let mut header = ClauseHeader::new();
+
+ header.set_glue(glue);
+ header.set_tier(select_tier(glue));
+
+ header
+}
+
+/// Compute the tier for a redundant clause with a given glue level.
+fn select_tier(glue: usize) -> Tier {
+ if glue <= 2 {
+ Tier::Core
+ } else if glue <= 6 {
+ Tier::Mid
+ } else {
+ Tier::Local
+ }
+}
+
+/// Update stats for clauses involved in the conflict.
+pub fn bump_clause(
+ mut ctx: partial!(
+ Context,
+ mut ClauseActivityP,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut TmpFlagsP,
+ ImplGraphP
+ ),
+ cref: ClauseRef,
+) {
+ bump_clause_activity(ctx.borrow(), cref);
+
+ let (alloc, mut ctx_2) = ctx.split_part_mut(ClauseAllocP);
+
+ let clause = alloc.clause_mut(cref);
+
+ let glue = compute_glue(ctx_2.borrow(), clause.lits());
+
+ clause.header_mut().set_active(true);
+
+ if glue < clause.header().glue() {
+ clause.header_mut().set_glue(glue);
+
+ db::set_clause_tier(ctx.borrow(), cref, select_tier(glue));
+ }
+}
diff --git a/vendor/varisat/src/clause/db.rs b/vendor/varisat/src/clause/db.rs
new file mode 100644
index 000000000..780e14557
--- /dev/null
+++ b/vendor/varisat/src/clause/db.rs
@@ -0,0 +1,269 @@
+//! Database for long clauses.
+use std::mem::transmute;
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+
+use crate::{
+ context::{parts::*, Context},
+ prop::Reason,
+};
+
+use super::{header::HEADER_LEN, ClauseAlloc, ClauseHeader, ClauseRef};
+
+/// Partitions of the clause database.
+///
+/// The long clauses are partitioned into 4 [`Tier`]s. This follows the approach described by
+/// Chanseok Oh in ["Between SAT and UNSAT: The Fundamental Difference in CDCL
+/// SAT"](https://doi.org/10.1007/978-3-319-24318-4_23), section 4.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Tier {
+ Irred = 0,
+ Core = 1,
+ Mid = 2,
+ Local = 3,
+}
+
+impl Tier {
+ /// Total number of tiers.
+ pub const fn count() -> usize {
+ 4
+ }
+
+ /// Cast an index into the corresponding tier.
+ pub unsafe fn from_index(index: usize) -> Tier {
+ debug_assert!(index < Tier::count());
+ transmute(index as u8)
+ }
+}
+
+/// Database for long clauses.
+///
+/// Removal of clauses from the `clauses` and the `by_tier` fields can be delayed. The clause
+/// header's deleted and tier fields need to be checked when iterating over these. `by_tier` may
+/// also contain duplicate entries.
+#[derive(Default)]
+pub struct ClauseDb {
+ /// May contain deleted clauses, see above
+ pub(super) clauses: Vec<ClauseRef>,
+ /// May contain deleted and moved clauses, see above
+ pub(super) by_tier: [Vec<ClauseRef>; Tier::count()],
+ /// These counts should always be up to date
+ pub(super) count_by_tier: [usize; Tier::count()],
+ /// Size of deleted but not collected clauses
+ pub(super) garbage_size: usize,
+}
+
+impl ClauseDb {
+ /// The number of long clauses of a given tier.
+ pub fn count_by_tier(&self, tier: Tier) -> usize {
+ self.count_by_tier[tier as usize]
+ }
+}
+
+/// Add a long clause to the database.
+pub fn add_clause(
+ mut ctx: partial!(Context, mut ClauseAllocP, mut ClauseDbP, mut WatchlistsP),
+ header: ClauseHeader,
+ lits: &[Lit],
+) -> ClauseRef {
+ let tier = header.tier();
+
+ let cref = ctx.part_mut(ClauseAllocP).add_clause(header, lits);
+
+ ctx.part_mut(WatchlistsP)
+ .watch_clause(cref, [lits[0], lits[1]]);
+
+ let db = ctx.part_mut(ClauseDbP);
+
+ db.clauses.push(cref);
+ db.by_tier[tier as usize].push(cref);
+ db.count_by_tier[tier as usize] += 1;
+
+ cref
+}
+
+/// Change the tier of a long clause.
+///
+/// This is a noop for a clause already of the specified tier.
+pub fn set_clause_tier(
+ mut ctx: partial!(Context, mut ClauseAllocP, mut ClauseDbP),
+ cref: ClauseRef,
+ tier: Tier,
+) {
+ let (alloc, mut ctx) = ctx.split_part_mut(ClauseAllocP);
+ let db = ctx.part_mut(ClauseDbP);
+
+ let old_tier = alloc.header(cref).tier();
+ if old_tier != tier {
+ db.count_by_tier[old_tier as usize] -= 1;
+ db.count_by_tier[tier as usize] += 1;
+
+ alloc.header_mut(cref).set_tier(tier);
+ db.by_tier[tier as usize].push(cref);
+ }
+}
+
+/// Delete a long clause from the database.
+pub fn delete_clause(
+ mut ctx: partial!(Context, mut ClauseAllocP, mut ClauseDbP, mut WatchlistsP),
+ cref: ClauseRef,
+) {
+ // TODO Don't force a rebuild of all watchlists here
+ ctx.part_mut(WatchlistsP).disable();
+
+ let (alloc, mut ctx) = ctx.split_part_mut(ClauseAllocP);
+ let db = ctx.part_mut(ClauseDbP);
+
+ let header = alloc.header_mut(cref);
+
+ debug_assert!(
+ !header.deleted(),
+ "delete_clause for already deleted clause"
+ );
+
+ header.set_deleted(true);
+
+ db.count_by_tier[header.tier() as usize] -= 1;
+
+ db.garbage_size += header.len() + HEADER_LEN;
+}
+
+/// Delete a long clause from the database unless it is asserting.
+///
+/// Returns true if the clause was deleted.
+pub fn try_delete_clause(
+ mut ctx: partial!(
+ Context,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut WatchlistsP,
+ ImplGraphP,
+ AssignmentP,
+ ),
+ cref: ClauseRef,
+) -> bool {
+ let initial_lit = ctx.part(ClauseAllocP).clause(cref).lits()[0];
+ let asserting = ctx.part(AssignmentP).lit_is_true(initial_lit)
+ && ctx.part(ImplGraphP).reason(initial_lit.var()) == &Reason::Long(cref);
+
+ if !asserting {
+ delete_clause(ctx.borrow(), cref);
+ }
+ !asserting
+}
+
+/// Iterator over all long clauses.
+///
+/// This filters deleted (but uncollected) clauses on the fly.
+pub fn clauses_iter<'a>(
+ ctx: &'a partial!('a Context, ClauseAllocP, ClauseDbP),
+) -> impl Iterator<Item = ClauseRef> + 'a {
+ let alloc = ctx.part(ClauseAllocP);
+ ctx.part(ClauseDbP)
+ .clauses
+ .iter()
+ .cloned()
+ .filter(move |&cref| !alloc.header(cref).deleted())
+}
+
+/// Iterate over all and remove some long clauses.
+///
+/// Takes a closure that returns true for each clause that should be kept and false for each that
+/// should be deleted.
+pub fn filter_clauses<F>(
+ mut ctx: partial!(Context, mut ClauseAllocP, mut ClauseDbP, mut WatchlistsP),
+ mut filter: F,
+) where
+ F: FnMut(&mut ClauseAlloc, ClauseRef) -> bool,
+{
+ ctx.part_mut(WatchlistsP).disable();
+
+ let (alloc, mut ctx) = ctx.split_part_mut(ClauseAllocP);
+ let db = ctx.part_mut(ClauseDbP);
+
+ let count_by_tier = &mut db.count_by_tier;
+ let garbage_size = &mut db.garbage_size;
+
+ db.clauses.retain(|&cref| {
+ if alloc.header(cref).deleted() {
+ false
+ } else if filter(alloc, cref) {
+ true
+ } else {
+ let header = alloc.header_mut(cref);
+
+ header.set_deleted(true);
+
+ count_by_tier[header.tier() as usize] -= 1;
+
+ *garbage_size += header.len() + HEADER_LEN;
+
+ false
+ }
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use partial_ref::IntoPartialRefMut;
+
+ use varisat_formula::cnf_formula;
+
+ use crate::context::set_var_count;
+
+ #[test]
+ fn set_tiers_and_deletes() {
+ let mut ctx = Context::default();
+
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ let clauses = cnf_formula![
+ 1, 2, 3;
+ 4, -5, 6;
+ -2, 3, -4;
+ -3, 5, 2, 7, 5;
+ ];
+
+ set_var_count(ctx.borrow(), clauses.var_count());
+
+ let tiers = vec![Tier::Irred, Tier::Core, Tier::Mid, Tier::Local];
+ let new_tiers = vec![Tier::Irred, Tier::Local, Tier::Local, Tier::Core];
+
+ let mut crefs = vec![];
+
+ for (clause, &tier) in clauses.iter().zip(tiers.iter()) {
+ let mut header = ClauseHeader::new();
+ header.set_tier(tier);
+ let cref = add_clause(ctx.borrow(), header, clause);
+ crefs.push(cref);
+ }
+
+ for (&cref, &tier) in crefs.iter().rev().zip(new_tiers.iter().rev()) {
+ set_clause_tier(ctx.borrow(), cref, tier);
+ }
+
+ // We only check presence, as deletion from these lists is delayed
+ assert!(ctx.part(ClauseDbP).by_tier[Tier::Irred as usize].contains(&crefs[0]));
+ assert!(ctx.part(ClauseDbP).by_tier[Tier::Core as usize].contains(&crefs[3]));
+ assert!(ctx.part(ClauseDbP).by_tier[Tier::Local as usize].contains(&crefs[1]));
+ assert!(ctx.part(ClauseDbP).by_tier[Tier::Local as usize].contains(&crefs[2]));
+
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Irred), 1);
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Core), 1);
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Mid), 0);
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Local), 2);
+
+ delete_clause(ctx.borrow(), crefs[0]);
+ delete_clause(ctx.borrow(), crefs[2]);
+
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Irred), 0);
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Core), 1);
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Mid), 0);
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Local), 1);
+ }
+}
diff --git a/vendor/varisat/src/clause/gc.rs b/vendor/varisat/src/clause/gc.rs
new file mode 100644
index 000000000..0ad116660
--- /dev/null
+++ b/vendor/varisat/src/clause/gc.rs
@@ -0,0 +1,207 @@
+//! Garbage collection of long clauses.
+use partial_ref::{partial, PartialRef};
+
+use crate::{
+ context::{parts::*, Context},
+ prop::Reason,
+};
+
+use super::{ClauseAlloc, Tier};
+
+/// Perform a garbage collection of long clauses if necessary.
+pub fn collect_garbage(
+ mut ctx: partial!(
+ Context,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut ImplGraphP,
+ mut WatchlistsP,
+ TrailP,
+ ),
+) {
+ let alloc = ctx.part(ClauseAllocP);
+ let db = ctx.part(ClauseDbP);
+
+ // Collecting when a fixed fraction of the allocation is garbage amortizes collection costs.
+ if db.garbage_size * 2 > alloc.buffer_size() {
+ collect_garbage_now(ctx.borrow());
+ }
+}
+
+/// Unconditionally perform a garbage collection of long clauses.
+///
+/// This needs to invalidate or update any other data structure containing references to
+/// clauses.
+fn collect_garbage_now(
+ mut ctx: partial!(
+ Context,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut ImplGraphP,
+ mut WatchlistsP,
+ TrailP,
+ ),
+) {
+ ctx.part_mut(WatchlistsP).disable();
+
+ mark_asserting_clauses(ctx.borrow());
+
+ let (db, mut ctx) = ctx.split_part_mut(ClauseDbP);
+ let (impl_graph, mut ctx) = ctx.split_part_mut(ImplGraphP);
+ let alloc = ctx.part_mut(ClauseAllocP);
+
+ assert!(
+ db.garbage_size <= alloc.buffer_size(),
+ "Inconsistent garbage tracking in ClauseDb"
+ );
+ let current_size = alloc.buffer_size() - db.garbage_size;
+
+ // Allocating just the current size would lead to an immediate growing when new clauses are
+ // learned, overallocating here avoids that.
+ let mut new_alloc = ClauseAlloc::with_capacity(current_size * 2);
+
+ let mut new_clauses = vec![];
+ let mut new_by_tier: [Vec<_>; Tier::count()] = Default::default();
+
+ // TODO Optimize order of clauses (benchmark this)
+
+ db.clauses.retain(|&cref| {
+ let clause = alloc.clause(cref);
+ let mut header = *clause.header();
+ if header.deleted() {
+ false
+ } else {
+ let clause_is_asserting = header.mark();
+ header.set_mark(false);
+
+ let new_cref = new_alloc.add_clause(header, clause.lits());
+
+ new_clauses.push(new_cref);
+ new_by_tier[header.tier() as usize].push(new_cref);
+
+ if clause_is_asserting {
+ let asserted_lit = clause.lits()[0];
+
+ debug_assert_eq!(impl_graph.reason(asserted_lit.var()), &Reason::Long(cref));
+ impl_graph.update_reason(asserted_lit.var(), Reason::Long(new_cref));
+ }
+ true
+ }
+ });
+
+ *ctx.part_mut(ClauseAllocP) = new_alloc;
+ db.clauses = new_clauses;
+ db.by_tier = new_by_tier;
+ db.garbage_size = 0;
+}
+
+/// Mark asserting clauses to track them through GC.
+fn mark_asserting_clauses(mut ctx: partial!(Context, mut ClauseAllocP, ImplGraphP, TrailP,)) {
+ let (trail, mut ctx) = ctx.split_part(TrailP);
+ let (alloc, ctx) = ctx.split_part_mut(ClauseAllocP);
+ let impl_graph = ctx.part(ImplGraphP);
+
+ for &lit in trail.trail().iter() {
+ if let Reason::Long(cref) = impl_graph.reason(lit.var()) {
+ alloc.header_mut(*cref).set_mark(true);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::cmp::max;
+
+ use partial_ref::IntoPartialRefMut;
+ use proptest::*;
+
+ use varisat_formula::{cnf::strategy::*, Lit};
+
+ use crate::{
+ clause::{db, ClauseHeader},
+ context::set_var_count,
+ prop::enqueue_assignment,
+ };
+
+ proptest! {
+ #[test]
+ fn garbage_collection(
+ input_a in cnf_formula(2..100usize, 500..1000, 3..30),
+ input_b in cnf_formula(2..100usize, 10..500, 4..20),
+ ) {
+ let mut ctx = Context::default();
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ set_var_count(ctx.borrow(), max(input_a.var_count(), input_b.var_count()));
+
+ let mut crefs_a = vec![];
+ let mut crefs_b = vec![];
+
+ for lits in input_a.iter() {
+ let header = ClauseHeader::new();
+ let cref = db::add_clause(ctx.borrow(), header, lits);
+ crefs_a.push(cref);
+ }
+
+ for lits in input_b.iter() {
+ let header = ClauseHeader::new();
+ let cref = db::add_clause(ctx.borrow(), header, lits);
+ crefs_b.push(cref);
+
+ if ctx.part(AssignmentP).lit_value(lits[0]) == None {
+ // This isn't consistent, as the clause isn't actually propagating, but that
+ // isn't checked during garbage collection
+ enqueue_assignment(ctx.borrow(), lits[0], Reason::Long(cref));
+ }
+ }
+
+ for cref in crefs_a {
+ db::delete_clause(ctx.borrow(), cref);
+ prop_assert!(ctx.part(ClauseDbP).garbage_size > 0);
+ }
+
+ let old_buffer_size = ctx.part(ClauseAllocP).buffer_size();
+
+ collect_garbage(ctx.borrow());
+
+ prop_assert!(
+ ctx.part(ClauseDbP).garbage_size * 2 < ctx.part(ClauseAllocP).buffer_size()
+ );
+
+ prop_assert!(
+ old_buffer_size > ctx.part(ClauseAllocP).buffer_size()
+ );
+
+ prop_assert!(!ctx.part(WatchlistsP).enabled());
+
+ let mut output_clauses: Vec<Vec<Lit>> = vec![];
+
+ for &cref in ctx.part(ClauseDbP).clauses.iter() {
+ let clause = ctx.part(ClauseAllocP).clause(cref);
+ if clause.header().deleted() {
+ continue;
+ }
+ prop_assert!(!clause.header().mark());
+ output_clauses.push(clause.lits().to_vec());
+ }
+
+ let mut input_clauses: Vec<Vec<Lit>> = input_b
+ .iter()
+ .map(|c| c.to_vec())
+ .collect();
+
+ output_clauses.sort();
+ input_clauses.sort();
+
+ prop_assert_eq!(input_clauses, output_clauses);
+
+ for &lit in ctx.part(TrailP).trail() {
+ if let Reason::Long(cref) = ctx.part(ImplGraphP).reason(lit.var()) {
+ prop_assert_eq!(ctx.part(ClauseAllocP).clause(*cref).lits()[0], lit)
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/varisat/src/clause/header.rs b/vendor/varisat/src/clause/header.rs
new file mode 100644
index 000000000..74656e8c1
--- /dev/null
+++ b/vendor/varisat/src/clause/header.rs
@@ -0,0 +1,148 @@
+//! Metadata stored in the header of each long clause.
+use std::cmp::min;
+
+use varisat_formula::{lit::LitIdx, Var};
+
+use super::Tier;
+
+/// Length of a [`ClauseHeader`] in multiples of [`LitIdx`]
+pub(super) const HEADER_LEN: usize = 3;
+
+const TIER_WORD: usize = HEADER_LEN - 2;
+const TIER_OFFSET: usize = 0;
+const TIER_MASK: LitIdx = 0b11;
+
+const DELETED_WORD: usize = HEADER_LEN - 2;
+const DELETED_OFFSET: usize = 2;
+
+const MARK_WORD: usize = HEADER_LEN - 2;
+const MARK_OFFSET: usize = 3;
+
+const GLUE_WORD: usize = HEADER_LEN - 2;
+const GLUE_OFFSET: usize = 4;
+const GLUE_MASK: LitIdx = (1 << 6) - 1;
+
+const ACTIVE_WORD: usize = HEADER_LEN - 2;
+const ACTIVE_OFFSET: usize = 10;
+
+const ACTIVITY_WORD: usize = HEADER_LEN - 3;
+
+/// Metadata for a clause.
+///
+/// This is stored in a [`ClauseAlloc`](super::ClauseAlloc) and thus must have a representation
+/// compatible with slice of [`LitIdx`] values.
+#[repr(transparent)]
+#[derive(Copy, Clone, Default)]
+pub struct ClauseHeader {
+ pub(super) data: [LitIdx; HEADER_LEN],
+}
+
+impl ClauseHeader {
+ /// Create a new clause header with default entries.
+ pub fn new() -> ClauseHeader {
+ Self::default()
+ }
+
+ /// Length of the clause.
+ pub fn len(&self) -> usize {
+ self.data[HEADER_LEN - 1] as usize
+ }
+
+ /// Set the length of the clause.
+ ///
+ /// Must be `<= Var::max_count()` as each variable may only be present once per clause.
+ pub fn set_len(&mut self, length: usize) {
+ debug_assert!(length <= Var::max_count());
+
+ self.data[HEADER_LEN - 1] = length as LitIdx;
+ }
+
+ /// Whether the clause is marked as deleted.
+ pub fn deleted(&self) -> bool {
+ (self.data[DELETED_WORD] >> DELETED_OFFSET) & 1 != 0
+ }
+
+ /// Mark the clause as deleted.
+ pub fn set_deleted(&mut self, deleted: bool) {
+ let word = &mut self.data[DELETED_WORD];
+ *word = (*word & !(1 << DELETED_OFFSET)) | ((deleted as LitIdx) << DELETED_OFFSET);
+ }
+
+ /// Current [`Tier`] of the clause.
+ pub fn tier(&self) -> Tier {
+ unsafe { Tier::from_index(((self.data[TIER_WORD] >> TIER_OFFSET) & TIER_MASK) as usize) }
+ }
+
+ /// Set the current [`Tier`] of the clause.
+ pub fn set_tier(&mut self, tier: Tier) {
+ let word = &mut self.data[TIER_WORD];
+ *word = (*word & !(TIER_MASK << TIER_OFFSET)) | ((tier as LitIdx) << TIER_OFFSET);
+ }
+
+ /// Whether the clause is redundant.
+ pub fn redundant(&self) -> bool {
+ self.tier() != Tier::Irred
+ }
+
+ /// Whether the clause is marked.
+ ///
+ /// The mark is a temporary bit that can be set by various routines, but should always be reset
+ /// to false.
+ pub fn mark(&self) -> bool {
+ (self.data[MARK_WORD] >> MARK_OFFSET) & 1 != 0
+ }
+
+ /// Mark or unmark the clause.
+ ///
+ /// Make sure to clear the mark after use.
+ pub fn set_mark(&mut self, mark: bool) {
+ let word = &mut self.data[MARK_WORD];
+ *word = (*word & !(1 << MARK_OFFSET)) | ((mark as LitIdx) << MARK_OFFSET);
+ }
+
+ /// The clause's active flag
+ ///
+ /// This is set when a clause was involved in conflict analysis and periodically reset.
+ pub fn active(&self) -> bool {
+ (self.data[ACTIVE_WORD] >> ACTIVE_OFFSET) & 1 != 0
+ }
+
+ /// Set or reset the clause's active flag.
+ pub fn set_active(&mut self, active: bool) {
+ let word = &mut self.data[ACTIVE_WORD];
+ *word = (*word & !(1 << ACTIVE_OFFSET)) | ((active as LitIdx) << ACTIVE_OFFSET);
+ }
+
+ /// The [glue][crate::glue] level.
+ pub fn glue(&self) -> usize {
+ ((self.data[GLUE_WORD] >> GLUE_OFFSET) & GLUE_MASK) as usize
+ }
+
+ /// Update the [glue][crate::glue] level.
+ pub fn set_glue(&mut self, glue: usize) {
+ let glue = min(glue, GLUE_MASK as usize) as LitIdx;
+ let word = &mut self.data[GLUE_WORD];
+
+ *word = (*word & !(GLUE_MASK << GLUE_OFFSET)) | (glue << GLUE_OFFSET);
+ }
+
+ /// Clause [activity][crate::clause::activity].
+ pub fn activity(&self) -> f32 {
+ f32::from_bits(self.data[ACTIVITY_WORD] as u32)
+ }
+
+ /// Update clause [activity][crate::clause::activity].
+ pub fn set_activity(&mut self, activity: f32) {
+ self.data[ACTIVITY_WORD] = activity.to_bits() as LitIdx;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn tier_mask() {
+ assert!(Tier::count() <= TIER_MASK as usize + 1);
+ }
+}
diff --git a/vendor/varisat/src/clause/reduce.rs b/vendor/varisat/src/clause/reduce.rs
new file mode 100644
index 000000000..1252ca50b
--- /dev/null
+++ b/vendor/varisat/src/clause/reduce.rs
@@ -0,0 +1,132 @@
+//! Clause database reduction.
+use std::mem::replace;
+
+use ordered_float::OrderedFloat;
+use vec_mut_scan::VecMutScan;
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_internal_proof::{DeleteClauseProof, ProofStep};
+
+use crate::{
+ context::{parts::*, Context},
+ proof,
+};
+
+use super::db::{set_clause_tier, try_delete_clause, Tier};
+
+/// Remove deleted and duplicate entries from the by_tier clause lists.
+///
+/// This has the side effect of setting the mark bit on all clauses of the tier.
+pub fn dedup_and_mark_by_tier(
+ mut ctx: partial!(Context, mut ClauseAllocP, mut ClauseDbP),
+ tier: Tier,
+) {
+ let (alloc, mut ctx) = ctx.split_part_mut(ClauseAllocP);
+ let by_tier = &mut ctx.part_mut(ClauseDbP).by_tier[tier as usize];
+
+ by_tier.retain(|&cref| {
+ let header = alloc.header_mut(cref);
+ let retain = !header.deleted() && !header.mark() && header.tier() == tier;
+ if retain {
+ header.set_mark(true);
+ }
+ retain
+ })
+}
+
+/// Reduce the number of local tier clauses by deleting half of them.
+pub fn reduce_locals<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut WatchlistsP,
+ AssignmentP,
+ ImplGraphP,
+ VariablesP,
+ ),
+) {
+ dedup_and_mark_by_tier(ctx.borrow(), Tier::Local);
+
+ let mut locals = replace(
+ &mut ctx.part_mut(ClauseDbP).by_tier[Tier::Local as usize],
+ vec![],
+ );
+
+ locals.sort_unstable_by_key(|&cref| {
+ (
+ OrderedFloat(ctx.part(ClauseAllocP).header(cref).activity()),
+ cref,
+ )
+ });
+
+ let mut to_delete = locals.len() / 2;
+
+ let mut scan = VecMutScan::new(&mut locals);
+
+ if to_delete > 0 {
+ while let Some(cref) = scan.next() {
+ ctx.part_mut(ClauseAllocP).header_mut(*cref).set_mark(false);
+
+ if try_delete_clause(ctx.borrow(), *cref) {
+ if ctx.part(ProofP).is_active() {
+ let (alloc, mut ctx) = ctx.split_part(ClauseAllocP);
+ let lits = alloc.clause(*cref).lits();
+ proof::add_step(
+ ctx.borrow(),
+ true,
+ &ProofStep::DeleteClause {
+ clause: lits,
+ proof: DeleteClauseProof::Redundant,
+ },
+ );
+ }
+
+ cref.remove();
+ to_delete -= 1;
+ if to_delete == 0 {
+ break;
+ }
+ }
+ }
+ }
+
+ // Make sure to clear all marks
+ while let Some(cref) = scan.next() {
+ ctx.part_mut(ClauseAllocP).header_mut(*cref).set_mark(false);
+ }
+
+ drop(scan);
+
+ ctx.part_mut(ClauseDbP).count_by_tier[Tier::Local as usize] = locals.len();
+ ctx.part_mut(ClauseDbP).by_tier[Tier::Local as usize] = locals;
+}
+
+/// Reduce the number of mid tier clauses by moving inactive ones to the local tier.
+pub fn reduce_mids(mut ctx: partial!(Context, mut ClauseAllocP, mut ClauseDbP)) {
+ dedup_and_mark_by_tier(ctx.borrow(), Tier::Mid);
+
+ let mut mids = replace(
+ &mut ctx.part_mut(ClauseDbP).by_tier[Tier::Mid as usize],
+ vec![],
+ );
+
+ mids.retain(|&cref| {
+ let header = ctx.part_mut(ClauseAllocP).header_mut(cref);
+ header.set_mark(false);
+
+ if header.active() {
+ header.set_active(false);
+ true
+ } else {
+ set_clause_tier(ctx.borrow(), cref, Tier::Local);
+ false
+ }
+ });
+
+ ctx.part_mut(ClauseDbP).count_by_tier[Tier::Mid as usize] = mids.len();
+ ctx.part_mut(ClauseDbP).by_tier[Tier::Mid as usize] = mids;
+}
diff --git a/vendor/varisat/src/config.rs b/vendor/varisat/src/config.rs
new file mode 100644
index 000000000..433b91ccf
--- /dev/null
+++ b/vendor/varisat/src/config.rs
@@ -0,0 +1,31 @@
+//! Solver configuration.
+use varisat_internal_macros::{ConfigUpdate, DocDefault};
+
+/// Configurable parameters used during solving.
+#[derive(DocDefault, ConfigUpdate)]
+pub struct SolverConfig {
+ /// Multiplicative decay for the VSIDS decision heuristic.
+ ///
+ /// [default: 0.95] [range: 0.5..1.0]
+ pub vsids_decay: f32,
+
+ /// Multiplicative decay for clause activities.
+ ///
+ /// [default: 0.999] [range: 0.5..1.0]
+ pub clause_activity_decay: f32,
+
+ /// Number of conflicts between local clause reductions.
+ ///
+ /// [default: 15000] [range: 1..]
+ pub reduce_locals_interval: u64,
+
+ /// Number of conflicts between mid clause reductions.
+ ///
+ /// [default: 10000] [range: 1..]
+ pub reduce_mids_interval: u64,
+
+ /// Scaling factor for luby sequence based restarts (number of conflicts).
+ ///
+ /// [default: 128] [range: 1..]
+ pub luby_restart_interval_scale: u64,
+}
diff --git a/vendor/varisat/src/context.rs b/vendor/varisat/src/context.rs
new file mode 100644
index 000000000..47a83c2e3
--- /dev/null
+++ b/vendor/varisat/src/context.rs
@@ -0,0 +1,131 @@
+//! Central solver data structure.
+//!
+//! This module defines the `Context` data structure which holds all data used by the solver. It
+//! also contains global notification functions that likely need to be extended when new parts are
+//! added to the solver.
+use partial_ref::{part, partial, PartialRef, PartialRefTarget};
+
+use crate::{
+ analyze_conflict::AnalyzeConflict,
+ assumptions::Assumptions,
+ binary::BinaryClauses,
+ clause::{ClauseActivity, ClauseAlloc, ClauseDb},
+ config::{SolverConfig, SolverConfigUpdate},
+ decision::vsids::Vsids,
+ model::Model,
+ proof::Proof,
+ prop::{Assignment, ImplGraph, Trail, Watchlists},
+ schedule::Schedule,
+ state::SolverState,
+ tmp::{TmpData, TmpFlags},
+ variables::Variables,
+};
+
+/// Part declarations for the [`Context`] struct.
+pub mod parts {
+ use super::*;
+
+ part!(pub AnalyzeConflictP: AnalyzeConflict);
+ part!(pub AssignmentP: Assignment);
+ part!(pub BinaryClausesP: BinaryClauses);
+ part!(pub ClauseActivityP: ClauseActivity);
+ part!(pub ClauseAllocP: ClauseAlloc);
+ part!(pub ClauseDbP: ClauseDb);
+ part!(pub ImplGraphP: ImplGraph);
+ part!(pub AssumptionsP: Assumptions);
+ part!(pub ModelP: Model);
+ part!(pub ProofP<'a>: Proof<'a>);
+ part!(pub ScheduleP: Schedule);
+ part!(pub SolverConfigP: SolverConfig);
+ part!(pub SolverStateP: SolverState);
+ part!(pub TmpDataP: TmpData);
+ part!(pub TmpFlagsP: TmpFlags);
+ part!(pub TrailP: Trail);
+ part!(pub VariablesP: Variables);
+ part!(pub VsidsP: Vsids);
+ part!(pub WatchlistsP: Watchlists);
+}
+
+use parts::*;
+
+/// Central solver data structure.
+///
+/// This struct contains all data kept by the solver. Most functions operating on multiple fields of
+/// the context use partial references provided by the `partial_ref` crate. This documents the data
+/// dependencies and makes the borrow checker happy without the overhead of passing individual
+/// references.
+#[derive(PartialRefTarget, Default)]
+pub struct Context<'a> {
+ #[part(AnalyzeConflictP)]
+ pub analyze_conflict: AnalyzeConflict,
+ #[part(AssignmentP)]
+ pub assignment: Assignment,
+ #[part(BinaryClausesP)]
+ pub binary_clauses: BinaryClauses,
+ #[part(ClauseActivityP)]
+ pub clause_activity: ClauseActivity,
+ #[part(ClauseAllocP)]
+ pub clause_alloc: ClauseAlloc,
+ #[part(ClauseDbP)]
+ pub clause_db: ClauseDb,
+ #[part(ImplGraphP)]
+ pub impl_graph: ImplGraph,
+ #[part(AssumptionsP)]
+ pub assumptions: Assumptions,
+ #[part(ModelP)]
+ pub model: Model,
+ #[part(ProofP<'a>)]
+ pub proof: Proof<'a>,
+ #[part(ScheduleP)]
+ pub schedule: Schedule,
+ #[part(SolverConfigP)]
+ pub solver_config: SolverConfig,
+ #[part(SolverStateP)]
+ pub solver_state: SolverState,
+ #[part(TmpDataP)]
+ pub tmp_data: TmpData,
+ #[part(TmpFlagsP)]
+ pub tmp_flags: TmpFlags,
+ #[part(TrailP)]
+ pub trail: Trail,
+ #[part(VariablesP)]
+ pub variables: Variables,
+ #[part(VsidsP)]
+ pub vsids: Vsids,
+ #[part(WatchlistsP)]
+ pub watchlists: Watchlists,
+}
+
+/// Update structures for a new variable count.
+pub fn set_var_count(
+ mut ctx: partial!(
+ Context,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut TmpFlagsP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ count: usize,
+) {
+ ctx.part_mut(AnalyzeConflictP).set_var_count(count);
+ ctx.part_mut(AssignmentP).set_var_count(count);
+ ctx.part_mut(BinaryClausesP).set_var_count(count);
+ ctx.part_mut(ImplGraphP).set_var_count(count);
+ ctx.part_mut(TmpFlagsP).set_var_count(count);
+ ctx.part_mut(VsidsP).set_var_count(count);
+ ctx.part_mut(WatchlistsP).set_var_count(count);
+}
+
+/// The solver configuration has changed.
+pub fn config_changed(
+ mut ctx: partial!(Context, mut VsidsP, mut ClauseActivityP, SolverConfigP),
+ _update: &SolverConfigUpdate,
+) {
+ let (config, mut ctx) = ctx.split_part(SolverConfigP);
+ ctx.part_mut(VsidsP).set_decay(config.vsids_decay);
+ ctx.part_mut(ClauseActivityP)
+ .set_decay(config.clause_activity_decay);
+}
diff --git a/vendor/varisat/src/decision.rs b/vendor/varisat/src/decision.rs
new file mode 100644
index 000000000..a8c8ed267
--- /dev/null
+++ b/vendor/varisat/src/decision.rs
@@ -0,0 +1,58 @@
+//! Decision heuristics.
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Var;
+
+use crate::{
+ context::{parts::*, Context},
+ prop::{enqueue_assignment, Reason},
+};
+
+pub mod vsids;
+
+/// Make a decision and enqueue it.
+///
+/// Returns `false` if no decision was made because all variables are assigned.
+pub fn make_decision(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut ImplGraphP,
+ mut TrailP,
+ mut VsidsP
+ ),
+) -> bool {
+ let (vsids, mut ctx) = ctx.split_part_mut(VsidsP);
+
+ if let Some(decision_var) = vsids.find(|&var| ctx.part(AssignmentP).var_value(var).is_none()) {
+ let decision = decision_var.lit(ctx.part(AssignmentP).last_var_value(decision_var));
+
+ ctx.part_mut(TrailP).new_decision_level();
+
+ enqueue_assignment(ctx.borrow(), decision, Reason::Unit);
+
+ true
+ } else {
+ false
+ }
+}
+
+/// Make a variable available for decisions.
+pub fn make_available(mut ctx: partial!(Context, mut VsidsP), var: Var) {
+ ctx.part_mut(VsidsP).make_available(var);
+}
+
+/// Initialize decision heuristics for a new variable.
+pub fn initialize_var(mut ctx: partial!(Context, mut VsidsP), var: Var, available: bool) {
+ ctx.part_mut(VsidsP).reset(var);
+
+ if available {
+ make_available(ctx.borrow(), var);
+ }
+}
+
+/// Remove a variable from the decision heuristics.
+pub fn remove_var(mut ctx: partial!(Context, mut VsidsP), var: Var) {
+ ctx.part_mut(VsidsP).make_unavailable(var);
+}
diff --git a/vendor/varisat/src/decision/vsids.rs b/vendor/varisat/src/decision/vsids.rs
new file mode 100644
index 000000000..e59656d1b
--- /dev/null
+++ b/vendor/varisat/src/decision/vsids.rs
@@ -0,0 +1,346 @@
+//! The VSIDS branching heuristic.
+//!
+//! The VSIDS (Variable State Independent Decaying Sum) branching heuristic keeps an activity value
+//! for each variable. For each conflict some variables are bumped, which means that their activity
+//! is increased by a constant. After bumping some variables, the activity of all variables is
+//! decayed by multiplying it with a constant below 1.
+//!
+//! When a decision is made, it branches on the vairable with the highest activity among the
+//! unassigned variables.
+//!
+//! There are a few variants that differ in which variables are bumped. Varisat follows Minisat (and
+//! others) by bumping all variables in the conflict clause and all variables resolved on during
+//! conflict analysis.
+
+use ordered_float::OrderedFloat;
+
+use varisat_formula::Var;
+
+use crate::config::SolverConfig;
+
+/// The VSIDS branching heuristic.
+///
+/// As an optimization instead of decaying all activities each conflict, the bump value is divided
+/// by the decay factor each conflict. When this would cause a value to overflow all activities and
+/// the bump value are scaled down. Apart from a scaling factor that is the same for all involved
+/// values, this is equivalent to the naive implementation. As we only care about the order of
+/// activities we can ignore the scaling factor.
+pub struct Vsids {
+ /// The activity of each variable.
+ activity: Vec<OrderedFloat<f32>>,
+ /// A binary heap of the variables.
+ heap: Vec<Var>,
+ /// The position in the binary heap for each variable.
+ position: Vec<Option<usize>>,
+ /// The value to add on bumping.
+ bump: f32,
+ /// The inverse of the decay factor.
+ inv_decay: f32,
+}
+
+impl Default for Vsids {
+ fn default() -> Vsids {
+ Vsids {
+ activity: vec![],
+ heap: vec![],
+ position: vec![],
+ bump: 1.0,
+ inv_decay: 1.0 / SolverConfig::default().vsids_decay,
+ }
+ }
+}
+
+impl Vsids {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.activity.resize(count, OrderedFloat(0.0));
+ self.position.resize(count, None);
+ }
+
+ /// Rescale activities if any value exceeds this value.
+ fn rescale_limit() -> f32 {
+ std::f32::MAX / 16.0
+ }
+
+ /// Change the decay factor.
+ pub fn set_decay(&mut self, decay: f32) {
+ assert!(decay < 1.0);
+ assert!(decay > 1.0 / 16.0);
+ self.inv_decay = 1.0 / decay;
+ }
+
+ /// Bump a variable by increasing its activity.
+ pub fn bump(&mut self, var: Var) {
+ let rescale = {
+ let value = &mut self.activity[var.index()];
+ value.0 += self.bump;
+ value.0 >= Self::rescale_limit()
+ };
+ if rescale {
+ self.rescale();
+ }
+ if let Some(pos) = self.position[var.index()] {
+ self.sift_up(pos);
+ }
+ }
+
+ /// Decay all variable activities.
+ pub fn decay(&mut self) {
+ self.bump *= self.inv_decay;
+ if self.bump >= Self::rescale_limit() {
+ self.rescale();
+ }
+ }
+
+ /// Rescale all values to avoid an overflow.
+ fn rescale(&mut self) {
+ let rescale_factor = 1.0 / Self::rescale_limit();
+ for activity in &mut self.activity {
+ activity.0 *= rescale_factor;
+ }
+ self.bump *= rescale_factor;
+ }
+
+ /// Reset the activity of an unavailable variable to zero.
+ ///
+ /// Panics if the variable is still available.
+ pub fn reset(&mut self, var: Var) {
+ assert!(self.position[var.index()].is_none());
+ self.activity[var.index()] = OrderedFloat(0.0);
+ }
+
+ /// Remove a variable from the heap if present.
+ pub fn make_unavailable(&mut self, var: Var) {
+ if let Some(position) = self.position[var.index()] {
+ self.heap.swap_remove(position);
+ if self.heap.len() > position {
+ let moved_var = self.heap[position];
+ self.position[moved_var.index()] = Some(position);
+ self.sift_down(position);
+ }
+ self.position[var.index()] = None;
+ }
+ }
+
+ /// Insert a variable into the heap if not already present.
+ pub fn make_available(&mut self, var: Var) {
+ if self.position[var.index()].is_none() {
+ let position = self.heap.len();
+ self.position[var.index()] = Some(position);
+ self.heap.push(var);
+ self.sift_up(position);
+ }
+ }
+
+ /// Move a variable closer to the root until the heap property is satisfied.
+ fn sift_up(&mut self, mut pos: usize) {
+ let var = self.heap[pos];
+ loop {
+ if pos == 0 {
+ return;
+ }
+ let parent_pos = (pos - 1) / 2;
+ let parent_var = self.heap[parent_pos];
+ if self.activity[parent_var.index()] >= self.activity[var.index()] {
+ return;
+ }
+ self.position[var.index()] = Some(parent_pos);
+ self.heap[parent_pos] = var;
+ self.position[parent_var.index()] = Some(pos);
+ self.heap[pos] = parent_var;
+ pos = parent_pos;
+ }
+ }
+
+ /// Move a variable away from the root until the heap property is satisfied.
+ fn sift_down(&mut self, mut pos: usize) {
+ let var = self.heap[pos];
+ loop {
+ let mut largest_pos = pos;
+ let mut largest_var = var;
+
+ let left_pos = pos * 2 + 1;
+ if left_pos < self.heap.len() {
+ let left_var = self.heap[left_pos];
+
+ if self.activity[largest_var.index()] < self.activity[left_var.index()] {
+ largest_pos = left_pos;
+ largest_var = left_var;
+ }
+ }
+
+ let right_pos = pos * 2 + 2;
+ if right_pos < self.heap.len() {
+ let right_var = self.heap[right_pos];
+
+ if self.activity[largest_var.index()] < self.activity[right_var.index()] {
+ largest_pos = right_pos;
+ largest_var = right_var;
+ }
+ }
+
+ if largest_pos == pos {
+ return;
+ }
+
+ self.position[var.index()] = Some(largest_pos);
+ self.heap[largest_pos] = var;
+ self.position[largest_var.index()] = Some(pos);
+ self.heap[pos] = largest_var;
+ pos = largest_pos;
+ }
+ }
+}
+
+impl Iterator for Vsids {
+ type Item = Var;
+
+ fn next(&mut self) -> Option<Var> {
+ if self.heap.is_empty() {
+ None
+ } else {
+ let var = self.heap.swap_remove(0);
+ if !self.heap.is_empty() {
+ let top_var = self.heap[0];
+ self.position[top_var.index()] = Some(0);
+ self.sift_down(0);
+ }
+ self.position[var.index()] = None;
+ Some(var)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use varisat_formula::var;
+
+ #[test]
+ fn rescale_bump() {
+ let mut vsids = Vsids::default();
+ vsids.set_var_count(4);
+ vsids.set_decay(1.0 / 8.0);
+
+ for _ in 0..4 {
+ vsids.next();
+ }
+
+ for i in 0..4 {
+ for _ in 0..i {
+ vsids.bump(Var::from_index(i));
+ }
+ }
+
+ for _ in 0..41 {
+ vsids.decay();
+ }
+
+ for _ in 0..30 {
+ vsids.bump(var!(4));
+ }
+
+ // Decay is a power of two so these values are exact
+ #[allow(clippy::float_cmp)]
+ {
+ assert_eq!(vsids.activity[0].0, 0.0);
+ assert_eq!(vsids.activity[2].0, vsids.activity[1].0 * 2.0);
+ assert!(vsids.activity[3] > vsids.activity[2]);
+ }
+ }
+
+ #[test]
+ fn rescale_decay() {
+ let mut vsids = Vsids::default();
+ vsids.set_var_count(4);
+ vsids.set_decay(1.0 / 8.0);
+
+ for _ in 0..4 {
+ vsids.next();
+ }
+
+ for i in 0..4 {
+ for _ in 0..i {
+ vsids.bump(Var::from_index(i));
+ }
+ }
+
+ for _ in 0..60 {
+ vsids.decay();
+ }
+
+ // Decay is a power of two so these values are exact
+ #[allow(clippy::float_cmp)]
+ {
+ assert_eq!(vsids.activity[0].0, 0.0);
+ assert_eq!(vsids.activity[2].0, vsids.activity[1].0 * 2.0);
+ assert_eq!(vsids.activity[3].0, vsids.activity[1].0 * 3.0);
+ }
+ }
+
+ #[test]
+ fn heap_sorts() {
+ let mut vsids = Vsids::default();
+ vsids.set_var_count(8);
+
+ for _ in 0..8 {
+ vsids.next();
+ }
+
+ for i in 0..8 {
+ for _ in 0..i {
+ vsids.bump(Var::from_index(i));
+ }
+ }
+
+ for i in 0..8 {
+ vsids.make_available(Var::from_index((i * 5) % 8));
+ }
+
+ for i in (0..8).rev() {
+ assert_eq!(vsids.next(), Some(Var::from_index(i)));
+ }
+ assert_eq!(vsids.next(), None);
+ }
+
+ #[test]
+ fn heap_bump() {
+ let mut vsids = Vsids::default();
+ vsids.set_var_count(8);
+ vsids.set_decay(1.0 / 8.0);
+
+ for _ in 0..8 {
+ vsids.next();
+ }
+
+ for i in 0..8 {
+ for _ in 0..i {
+ vsids.bump(Var::from_index(i));
+ }
+ }
+
+ for i in 0..8 {
+ vsids.make_available(Var::from_index((i * 5) % 8));
+ }
+
+ for i in (0..4).rev() {
+ assert_eq!(vsids.next(), Some(Var::from_index(i + 4)));
+ }
+
+ vsids.decay();
+ vsids.decay();
+
+ for i in 0..8 {
+ for _ in 0..(8 - i) {
+ vsids.bump(Var::from_index(i));
+ }
+ }
+
+ for i in 0..4 {
+ assert_eq!(vsids.next(), Some(Var::from_index(i)));
+ }
+
+ assert_eq!(vsids.next(), None);
+ }
+}
diff --git a/vendor/varisat/src/glue.rs b/vendor/varisat/src/glue.rs
new file mode 100644
index 000000000..2036cdd99
--- /dev/null
+++ b/vendor/varisat/src/glue.rs
@@ -0,0 +1,36 @@
+//! Compute glue levels of clauses.
+//!
+//! The glue level of a propagating clause is the number of distinct decision levels of the clause's
+//! variables. This is also called the literal block distance (LBD). For each clause the smallest
+//! glue level observed is used as an indicator of how useful that clause is.
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+
+use crate::context::{parts::*, Context};
+
+/// Compute the glue level of a clause.
+pub fn compute_glue(mut ctx: partial!(Context, mut TmpFlagsP, ImplGraphP), lits: &[Lit]) -> usize {
+ let (tmp_flags, ctx) = ctx.split_part_mut(TmpFlagsP);
+ let impl_graph = ctx.part(ImplGraphP);
+ let flags = &mut tmp_flags.flags;
+
+ let mut glue = 0;
+
+ for &lit in lits {
+ let level = impl_graph.level(lit.var());
+ let flag = &mut flags[level];
+ if !*flag {
+ *flag = true;
+ glue += 1
+ }
+ }
+
+ for &lit in lits {
+ let level = impl_graph.level(lit.var());
+ flags[level] = false;
+ }
+
+ glue
+}
diff --git a/vendor/varisat/src/lib.rs b/vendor/varisat/src/lib.rs
new file mode 100644
index 000000000..340b57b6f
--- /dev/null
+++ b/vendor/varisat/src/lib.rs
@@ -0,0 +1,46 @@
+//! Varisat is a [CDCL][cdcl] based SAT solver written in rust. Given a boolean formula in
+//! [conjunctive normal form][cnf], it either finds a variable assignment that makes the formula
+//! true or finds a proof that this is impossible.
+//!
+//! In addition to this API documentation, Varisat comes with a [user manual].
+//!
+//! [cdcl]: https://en.wikipedia.org/wiki/Conflict-Driven_Clause_Learning
+//! [cnf]: https://en.wikipedia.org/wiki/Conjunctive_normal_form
+//! [user manual]: https://jix.github.io/varisat/manual/0.2.1/
+
+pub mod config;
+pub mod solver;
+
+mod analyze_conflict;
+mod assumptions;
+mod binary;
+mod cdcl;
+mod clause;
+mod context;
+mod decision;
+mod glue;
+mod load;
+mod model;
+mod proof;
+mod prop;
+mod schedule;
+mod state;
+mod tmp;
+mod unit_simplify;
+mod variables;
+
+pub use solver::{ProofFormat, Solver};
+pub use varisat_formula::{cnf, lit, CnfFormula, ExtendFormula, Lit, Var};
+
+pub mod dimacs {
+ //! DIMCAS CNF parser and writer.
+ pub use varisat_dimacs::*;
+}
+
+pub mod checker {
+ //! Proof checker for Varisat proofs.
+ pub use varisat_checker::{
+ CheckedProofStep, Checker, CheckerData, CheckerError, ProofProcessor,
+ ProofTranscriptProcessor, ProofTranscriptStep,
+ };
+}
diff --git a/vendor/varisat/src/load.rs b/vendor/varisat/src/load.rs
new file mode 100644
index 000000000..43415dbfb
--- /dev/null
+++ b/vendor/varisat/src/load.rs
@@ -0,0 +1,259 @@
+//! Loading a formula into the solver.
+use vec_mut_scan::VecMutScan;
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+use varisat_internal_proof::{DeleteClauseProof, ProofStep};
+
+use crate::{
+ clause::{db, ClauseHeader, Tier},
+ context::{parts::*, Context},
+ proof,
+ prop::{assignment, full_restart, Reason},
+ state::SatState,
+ unit_simplify::resurrect_unit,
+ variables,
+};
+
+/// Adds a clause to the current formula.
+///
+/// The input uses user variable names.
+///
+/// Removes duplicated literals, ignores tautological clauses (eg. x v -x v y), handles empty
+/// clauses and dispatches among unit, binary and long clauses.
+pub fn load_clause<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut AssumptionsP,
+ mut BinaryClausesP,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpDataP,
+ mut TmpFlagsP,
+ mut TrailP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ user_lits: &[Lit],
+) {
+ match ctx.part(SolverStateP).sat_state {
+ SatState::Unsat => return,
+ SatState::Sat => {
+ ctx.part_mut(SolverStateP).sat_state = SatState::Unknown;
+ }
+ _ => {}
+ }
+
+ ctx.part_mut(SolverStateP).formula_is_empty = false;
+
+ // Restart the search when the user adds new clauses.
+ full_restart(ctx.borrow());
+
+ // Convert the clause from user to solver literals.
+ let (tmp_data, mut ctx_variables) = ctx.split_part_mut(TmpDataP);
+ variables::solver_from_user_lits(ctx_variables.borrow(), &mut tmp_data.lits, user_lits, true);
+
+ let (tmp_data, mut ctx) = ctx.split_part_mut(TmpDataP);
+
+ let lits = &mut tmp_data.lits;
+ let false_lits = &mut tmp_data.lits_2;
+
+ lits.sort_unstable();
+ lits.dedup();
+
+ proof::add_clause(ctx.borrow(), &lits);
+
+ // Detect tautological clauses
+ let mut last = None;
+
+ for &lit in lits.iter() {
+ if last == Some(!lit) {
+ return;
+ }
+ last = Some(lit);
+ }
+
+ // If we're not a unit clause the contained variables are not isolated anymore.
+ if lits.len() > 1 {
+ for &lit in lits.iter() {
+ ctx.part_mut(VariablesP)
+ .var_data_solver_mut(lit.var())
+ .isolated = false;
+ }
+ }
+
+ // Remove satisfied clauses and handle false literals.
+ //
+ // Proof generation expects us to start with the actual input clauses. If we would remove false
+ // literals we would have to generate proof steps for that. This would result in derived clauses
+ // being added during loading. If we're running proof processors on the fly, they'd see those
+ // derived clauses interspersed with the input clauses.
+ //
+ // We don't want to require each proof processor to handle dervied clause additions during
+ // loading of the initial formula. Thus we need to handle clauses with false literals here.
+ false_lits.clear();
+
+ let mut lits_scan = VecMutScan::new(lits);
+
+ let mut clause_is_true = false;
+
+ // We move unassigned literals to the beginning to make sure we're going to watch unassigned
+ // literals.
+ while let Some(lit) = lits_scan.next() {
+ match ctx.part(AssignmentP).lit_value(*lit) {
+ Some(true) => {
+ clause_is_true = true;
+ break;
+ }
+ Some(false) => {
+ false_lits.push(lit.remove());
+ }
+ None => (),
+ }
+ }
+
+ drop(lits_scan);
+
+ let will_conflict = lits.is_empty();
+
+ // We resurrect any removed false literals to ensure propagation by this new clause. This is
+ // also required to eventually simplify this clause.
+ for &lit in false_lits.iter() {
+ resurrect_unit(ctx.borrow(), !lit);
+ }
+
+ lits.extend_from_slice(&false_lits);
+
+ if clause_is_true {
+ if lits.len() > 1 {
+ proof::add_step(
+ ctx.borrow(),
+ true,
+ &ProofStep::DeleteClause {
+ clause: lits,
+ proof: DeleteClauseProof::Satisfied,
+ },
+ );
+ }
+ return;
+ }
+
+ match lits[..] {
+ [] => ctx.part_mut(SolverStateP).sat_state = SatState::Unsat,
+ [lit] => {
+ if will_conflict {
+ ctx.part_mut(SolverStateP).sat_state = SatState::Unsat
+ } else {
+ assignment::enqueue_assignment(ctx.borrow(), lit, Reason::Unit)
+ }
+ }
+ [lit_0, lit_1] => {
+ ctx.part_mut(BinaryClausesP)
+ .add_binary_clause([lit_0, lit_1]);
+ }
+ _ => {
+ let mut header = ClauseHeader::new();
+ header.set_tier(Tier::Irred);
+
+ db::add_clause(ctx.borrow(), header, lits);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use partial_ref::IntoPartialRefMut;
+
+ use varisat_formula::lits;
+
+ use crate::clause::Tier;
+
+ #[test]
+ fn unsat_on_empty_clause() {
+ let mut ctx = Context::default();
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ load_clause(ctx.borrow(), &[]);
+
+ assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unsat);
+ }
+
+ #[test]
+ fn unit_clauses() {
+ let mut ctx = Context::default();
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ load_clause(ctx.borrow(), &lits![1]);
+
+ assert_eq!(ctx.part(TrailP).trail().len(), 1);
+
+ load_clause(ctx.borrow(), &lits![3, -3]);
+
+ assert_eq!(ctx.part(TrailP).trail().len(), 1);
+
+ load_clause(ctx.borrow(), &lits![-2]);
+
+ assert_eq!(ctx.part(TrailP).trail().len(), 2);
+
+ load_clause(ctx.borrow(), &lits![1, 1]);
+
+ assert_eq!(ctx.part(TrailP).trail().len(), 2);
+
+ assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
+
+ load_clause(ctx.borrow(), &lits![2]);
+
+ assert_eq!(ctx.part(TrailP).trail().len(), 2);
+
+ assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unsat);
+ }
+
+ #[test]
+ fn binary_clauses() {
+ let mut ctx = Context::default();
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ load_clause(ctx.borrow(), &lits![1, 2]);
+
+ assert_eq!(ctx.part(BinaryClausesP).count(), 1);
+
+ load_clause(ctx.borrow(), &lits![-1, 3, 3]);
+
+ assert_eq!(ctx.part(BinaryClausesP).count(), 2);
+
+ load_clause(ctx.borrow(), &lits![4, -4]);
+
+ assert_eq!(ctx.part(BinaryClausesP).count(), 2);
+
+ assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
+ }
+
+ #[test]
+ fn long_clauses() {
+ let mut ctx = Context::default();
+ let mut ctx = ctx.into_partial_ref_mut();
+
+ load_clause(ctx.borrow(), &lits![1, 2, 3]);
+
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Irred), 1);
+
+ load_clause(ctx.borrow(), &lits![-2, 3, 3, 4]);
+
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Irred), 2);
+
+ load_clause(ctx.borrow(), &lits![4, -5, 5, 2]);
+
+ assert_eq!(ctx.part(ClauseDbP).count_by_tier(Tier::Irred), 2);
+
+ assert_eq!(ctx.part(SolverStateP).sat_state, SatState::Unknown);
+ }
+}
diff --git a/vendor/varisat/src/model.rs b/vendor/varisat/src/model.rs
new file mode 100644
index 000000000..d4f269f6f
--- /dev/null
+++ b/vendor/varisat/src/model.rs
@@ -0,0 +1,87 @@
+//! Global model reconstruction
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+use varisat_internal_proof::ProofStep;
+
+use crate::{
+ context::{parts::*, Context},
+ proof,
+ state::SatState,
+};
+
+/// Global model reconstruction
+#[derive(Default)]
+pub struct Model {
+ /// Assignment of the global model.
+ ///
+ /// Whenever the solver state is SAT this must be up to date.
+ assignment: Vec<Option<bool>>,
+}
+
+impl Model {
+ /// Assignment of the global model.
+ ///
+ /// Only valid if the solver state is SAT.
+ pub fn assignment(&self) -> &[Option<bool>] {
+ &self.assignment
+ }
+
+ /// Whether a given global literal is true in the model assignment.
+ ///
+ /// Only valid if the solver state is SAT.
+ pub fn lit_is_true(&self, global_lit: Lit) -> bool {
+ self.assignment[global_lit.index()] == Some(global_lit.is_positive())
+ }
+}
+
+pub fn reconstruct_global_model<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut ModelP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpDataP,
+ AssignmentP,
+ VariablesP
+ ),
+) {
+ let (variables, mut ctx) = ctx.split_part(VariablesP);
+ let (model, mut ctx) = ctx.split_part_mut(ModelP);
+ let (tmp, mut ctx) = ctx.split_part_mut(TmpDataP);
+
+ let models_in_proof = ctx.part(ProofP).models_in_proof();
+
+ tmp.lits.clear();
+
+ model.assignment.clear();
+ model.assignment.resize(variables.global_watermark(), None);
+
+ for global_var in variables.global_var_iter() {
+ let value = if let Some(solver_var) = variables.solver_from_global().get(global_var) {
+ ctx.part(AssignmentP).var_value(solver_var)
+ } else {
+ Some(variables.var_data_global(global_var).unit.unwrap_or(false))
+ };
+
+ model.assignment[global_var.index()] = value;
+
+ if models_in_proof {
+ if let Some(value) = value {
+ tmp.lits.push(global_var.lit(value))
+ }
+ }
+ }
+
+ if models_in_proof {
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::Model {
+ assignment: &tmp.lits,
+ },
+ );
+ }
+ ctx.part_mut(SolverStateP).sat_state = SatState::Sat;
+}
diff --git a/vendor/varisat/src/proof.rs b/vendor/varisat/src/proof.rs
new file mode 100644
index 000000000..336f7fbc2
--- /dev/null
+++ b/vendor/varisat/src/proof.rs
@@ -0,0 +1,434 @@
+//! Proof generation.
+
+use std::io::{self, sink, BufWriter, Write};
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_checker::{internal::SelfChecker, Checker, CheckerError, ProofProcessor};
+use varisat_formula::{Lit, Var};
+use varisat_internal_proof::{ClauseHash, ProofStep};
+
+use crate::{
+ context::{parts::*, Context},
+ solver::SolverError,
+};
+
+mod drat;
+mod map_step;
+
+/// Proof formats that can be generated during solving.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum ProofFormat {
+ Varisat,
+ Drat,
+ BinaryDrat,
+}
+
+/// Number of added or removed clauses.
+pub fn clause_count_delta(step: &ProofStep) -> isize {
+ match step {
+ ProofStep::AddClause { clause } | ProofStep::AtClause { clause, .. } => {
+ if clause.len() > 1 {
+ 1
+ } else {
+ 0
+ }
+ }
+ ProofStep::DeleteClause { clause, .. } => {
+ if clause.len() > 1 {
+ -1
+ } else {
+ 0
+ }
+ }
+ ProofStep::SolverVarName { .. }
+ | ProofStep::UserVarName { .. }
+ | ProofStep::DeleteVar { .. }
+ | ProofStep::ChangeSamplingMode { .. }
+ | ProofStep::UnitClauses { .. }
+ | ProofStep::ChangeHashBits { .. }
+ | ProofStep::Model { .. }
+ | ProofStep::Assumptions { .. }
+ | ProofStep::FailedAssumptions { .. }
+ | ProofStep::End => 0,
+ }
+}
+
+/// Proof generation.
+pub struct Proof<'a> {
+ format: Option<ProofFormat>,
+ target: BufWriter<Box<dyn Write + 'a>>,
+ checker: Option<Checker<'a>>,
+ map_step: map_step::MapStep,
+ /// How many bits are used for storing clause hashes.
+ hash_bits: u32,
+ /// How many clauses are currently in the db.
+ ///
+ /// This is used to pick a good number of hash_bits
+ clause_count: isize,
+}
+
+impl<'a> Default for Proof<'a> {
+ fn default() -> Proof<'a> {
+ Proof {
+ format: None,
+ target: BufWriter::new(Box::new(sink())),
+ checker: None,
+ map_step: Default::default(),
+ hash_bits: 64,
+ clause_count: 0,
+ }
+ }
+}
+
+impl<'a> Proof<'a> {
+ /// Start writing proof steps to the given target with the given format.
+ pub fn write_proof(&mut self, target: impl Write + 'a, format: ProofFormat) {
+ self.format = Some(format);
+ self.target = BufWriter::new(Box::new(target))
+ }
+
+ /// Begin checking proof steps.
+ pub fn begin_checking(&mut self) {
+ if self.checker.is_none() {
+ self.checker = Some(Checker::new())
+ }
+ }
+
+ /// Add a [`ProofProcessor`].
+ ///
+ /// See also [`Checker::add_processor`].
+ pub fn add_processor(&mut self, processor: &'a mut dyn ProofProcessor) {
+ self.begin_checking();
+ self.checker.as_mut().unwrap().add_processor(processor);
+ }
+
+ /// Whether proof generation is active.
+ pub fn is_active(&self) -> bool {
+ self.checker.is_some() || self.format.is_some()
+ }
+
+ /// Are we emitting or checking our native format.
+ pub fn native_format(&self) -> bool {
+ self.checker.is_some()
+ || match self.format {
+ Some(ProofFormat::Varisat) => true,
+ _ => false,
+ }
+ }
+
+ /// Whether clause hashes are required for steps that support them.
+ pub fn clause_hashes_required(&self) -> bool {
+ self.native_format()
+ }
+
+ /// Whether found models are included in the proof.
+ pub fn models_in_proof(&self) -> bool {
+ self.native_format()
+ }
+}
+
+/// Call when adding an external clause.
+///
+/// This is required for on the fly checking and checking of incremental solving.
+///
+/// *Note:* this function expects the clause to use solver var names.
+pub fn add_clause<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, VariablesP),
+ clause: &[Lit],
+) {
+ if ctx.part(SolverStateP).solver_invoked {
+ add_step(ctx.borrow(), true, &ProofStep::AddClause { clause })
+ } else {
+ let (variables, mut ctx) = ctx.split_part(VariablesP);
+ let proof = ctx.part_mut(ProofP);
+ if let Some(checker) = &mut proof.checker {
+ let clause = proof.map_step.map_lits(clause, |var| {
+ variables
+ .global_from_solver()
+ .get(var)
+ .expect("no existing global var for solver var")
+ });
+
+ let result = checker.add_clause(clause);
+ handle_self_check_result(ctx.borrow(), result);
+ }
+ if clause.len() > 1 {
+ ctx.part_mut(ProofP).clause_count += 1;
+ }
+ }
+}
+
+/// Add a step to the proof.
+///
+/// Ignored when proof generation is disabled.
+///
+/// When `solver_vars` is true, all variables and literals will be converted from solver to global
+/// vars. Otherwise the proof step needs to use global vars.
+pub fn add_step<'a, 's>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, VariablesP),
+ solver_vars: bool,
+ step: &'s ProofStep<'s>,
+) {
+ if ctx.part(SolverStateP).solver_error.is_some() {
+ return;
+ }
+
+ if ctx.part(SolverStateP).solver_error.is_some() {
+ return;
+ }
+
+ let (variables, mut ctx) = ctx.split_part(VariablesP);
+ let proof = ctx.part_mut(ProofP);
+
+ let map_vars = |var| {
+ if solver_vars {
+ variables
+ .global_from_solver()
+ .get(var)
+ .expect("no existing global var for solver var")
+ } else {
+ var
+ }
+ };
+
+ let io_result = match proof.format {
+ Some(ProofFormat::Varisat) => write_varisat_step(ctx.borrow(), map_vars, step),
+ Some(ProofFormat::Drat) => {
+ let step = proof.map_step.map(step, map_vars, |hash| hash);
+ drat::write_step(&mut proof.target, &step)
+ }
+ Some(ProofFormat::BinaryDrat) => {
+ let step = proof.map_step.map(step, map_vars, |hash| hash);
+ drat::write_binary_step(&mut proof.target, &step)
+ }
+ None => Ok(()),
+ };
+
+ if io_result.is_ok() {
+ let proof = ctx.part_mut(ProofP);
+ if let Some(checker) = &mut proof.checker {
+ let step = proof.map_step.map(step, map_vars, |hash| hash);
+ let result = checker.self_check_step(step);
+ handle_self_check_result(ctx.borrow(), result);
+ }
+ }
+
+ handle_io_errors(ctx.borrow(), io_result);
+}
+
+/// Write a step using our native format
+fn write_varisat_step<'a, 's>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, SolverStateP),
+ map_vars: impl Fn(Var) -> Var,
+ step: &'s ProofStep<'s>,
+) -> io::Result<()> {
+ let (proof, ctx) = ctx.split_part_mut(ProofP);
+
+ proof.clause_count += clause_count_delta(step);
+
+ let mut rehash = false;
+ // Should we change the hash size?
+ while proof.clause_count > (1 << (proof.hash_bits / 2)) {
+ proof.hash_bits += 2;
+ rehash = true;
+ }
+ if ctx.part(SolverStateP).solver_invoked {
+ while proof.hash_bits > 6 && proof.clause_count * 4 < (1 << (proof.hash_bits / 2)) {
+ proof.hash_bits -= 2;
+ rehash = true;
+ }
+ }
+
+ if rehash {
+ varisat_internal_proof::binary_format::write_step(
+ &mut proof.target,
+ &ProofStep::ChangeHashBits {
+ bits: proof.hash_bits,
+ },
+ )?;
+ }
+
+ let shift_bits = ClauseHash::max_value().count_ones() - proof.hash_bits;
+
+ let map_hash = |hash| hash >> shift_bits;
+ let step = proof.map_step.map(step, map_vars, map_hash);
+
+ if proof.format == Some(ProofFormat::Varisat) {
+ varisat_internal_proof::binary_format::write_step(&mut proof.target, &step)?;
+ }
+
+ Ok(())
+}
+
+/// Flush buffers used for writing proof steps.
+pub fn flush_proof<'a>(mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP)) {
+ // We need to explicitly flush to handle IO errors.
+ let result = ctx.part_mut(ProofP).target.flush();
+ handle_io_errors(ctx.borrow(), result);
+}
+
+/// Stop writing proof steps.
+pub fn close_proof<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, VariablesP),
+) {
+ add_step(ctx.borrow(), true, &ProofStep::End);
+ flush_proof(ctx.borrow());
+ ctx.part_mut(ProofP).format = None;
+ ctx.part_mut(ProofP).target = BufWriter::new(Box::new(sink()));
+}
+
+/// Called before solve returns to flush buffers and to trigger delayed unit conflict processing.
+///
+/// We flush buffers before solve returns to ensure that we can pass IO errors to the user.
+pub fn solve_finished<'a>(mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP)) {
+ flush_proof(ctx.borrow());
+ if let Some(checker) = &mut ctx.part_mut(ProofP).checker {
+ let result = checker.self_check_delayed_steps();
+ handle_self_check_result(ctx.borrow(), result);
+ }
+}
+
+/// Handle results of on the fly checking.
+///
+/// Panics when the proof is incorrect and aborts solving when a proof processor produced an error.
+fn handle_self_check_result<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP),
+ result: Result<(), CheckerError>,
+) {
+ match result {
+ Err(CheckerError::ProofProcessorError { cause }) => {
+ ctx.part_mut(SolverStateP).solver_error =
+ Some(SolverError::ProofProcessorError { cause });
+ *ctx.part_mut(ProofP) = Proof::default();
+ }
+ Err(err) => {
+ log::error!("{}", err);
+ if let CheckerError::CheckFailed { debug_step, .. } = err {
+ if !debug_step.is_empty() {
+ log::error!("failed step was {}", debug_step)
+ }
+ }
+ panic!("self check failure");
+ }
+ Ok(()) => (),
+ }
+}
+
+/// Handle io errors during proof writing.
+fn handle_io_errors<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP),
+ result: io::Result<()>,
+) {
+ if let Err(io_err) = result {
+ ctx.part_mut(SolverStateP).solver_error = Some(SolverError::ProofIoError { cause: io_err });
+ *ctx.part_mut(ProofP) = Proof::default();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use proptest::prelude::*;
+
+ use std::{fs::File, process::Command};
+
+ use tempfile::TempDir;
+
+ use varisat_dimacs::write_dimacs;
+ use varisat_formula::{test::sgen_unsat_formula, CnfFormula};
+
+ use crate::solver::Solver;
+
+ enum Checker {
+ DratTrim,
+ Rate,
+ }
+
+ fn test_drat(checker: Checker, formula: CnfFormula, binary: bool) -> Result<(), TestCaseError> {
+ let mut solver = Solver::new();
+
+ let tmp = TempDir::new()?;
+
+ let drat_proof = tmp.path().join("proof.drat");
+ let cnf_file = tmp.path().join("input.cnf");
+
+ write_dimacs(&mut File::create(&cnf_file)?, &formula)?;
+
+ let format = if binary {
+ ProofFormat::BinaryDrat
+ } else {
+ ProofFormat::Drat
+ };
+
+ solver.write_proof(File::create(&drat_proof)?, format);
+
+ solver.add_formula(&formula);
+
+ prop_assert_eq!(solver.solve().ok(), Some(false));
+
+ solver
+ .close_proof()
+ .map_err(|e| TestCaseError::fail(e.to_string()))?;
+
+ let output = match checker {
+ Checker::DratTrim => {
+ if binary {
+ Command::new("drat-trim")
+ .arg(&cnf_file)
+ .arg(&drat_proof)
+ .arg("-i")
+ .output()?
+ } else {
+ Command::new("drat-trim")
+ .arg(&cnf_file)
+ .arg(&drat_proof)
+ .output()?
+ }
+ }
+ Checker::Rate => Command::new("rate")
+ .arg(&cnf_file)
+ .arg(&drat_proof)
+ .output()?,
+ };
+
+ prop_assert!(std::str::from_utf8(&output.stdout)?.contains("s VERIFIED"));
+
+ Ok(())
+ }
+
+ proptest! {
+ #[cfg_attr(not(test_drat_trim), ignore)]
+ #[test]
+ fn sgen_unsat_drat_trim(
+ formula in sgen_unsat_formula(1..7usize),
+ ) {
+ test_drat(Checker::DratTrim, formula, false)?;
+ }
+
+ #[cfg_attr(not(test_drat_trim), ignore)]
+ #[test]
+ fn sgen_unsat_binary_drat_trim(
+ formula in sgen_unsat_formula(1..7usize),
+ ) {
+ test_drat(Checker::DratTrim, formula, true)?;
+ }
+
+ #[cfg_attr(not(test_rate), ignore)]
+ #[test]
+ fn sgen_unsat_rate(
+ formula in sgen_unsat_formula(1..7usize),
+ ) {
+ test_drat(Checker::Rate, formula, false)?;
+ }
+
+ #[cfg_attr(not(test_rate), ignore)]
+ #[test]
+ fn sgen_unsat_binary_rate(
+ formula in sgen_unsat_formula(1..7usize),
+ ) {
+ test_drat(Checker::Rate, formula, true)?;
+ }
+ }
+}
diff --git a/vendor/varisat/src/proof/drat.rs b/vendor/varisat/src/proof/drat.rs
new file mode 100644
index 000000000..76382fab3
--- /dev/null
+++ b/vendor/varisat/src/proof/drat.rs
@@ -0,0 +1,85 @@
+use std::io::{self, Write};
+
+use varisat_formula::Lit;
+use varisat_internal_proof::ProofStep;
+
+/// Prepares a proof step for DRAT writing
+fn drat_step(
+ step: &ProofStep,
+ mut emit_drat_step: impl FnMut(bool, &[Lit]) -> io::Result<()>,
+) -> io::Result<()> {
+ match step {
+ ProofStep::AtClause { clause, .. } => {
+ emit_drat_step(true, &clause)?;
+ }
+ ProofStep::UnitClauses { units } => {
+ for &(unit, _hash) in units.iter() {
+ emit_drat_step(true, &[unit])?;
+ }
+ }
+ ProofStep::DeleteClause { clause, .. } => {
+ emit_drat_step(false, &clause[..])?;
+ }
+ ProofStep::SolverVarName { .. }
+ | ProofStep::UserVarName { .. }
+ | ProofStep::DeleteVar { .. }
+ | ProofStep::ChangeSamplingMode { .. }
+ | ProofStep::ChangeHashBits { .. }
+ | ProofStep::Model { .. }
+ | ProofStep::End => (),
+ ProofStep::AddClause { .. } => {
+ // TODO allow error handling here?
+ panic!("incremental clause additions not supported by DRAT proofs");
+ }
+ ProofStep::Assumptions { .. } | ProofStep::FailedAssumptions { .. } => {
+ // TODO allow error handling here?
+ panic!("assumptions not supported by DRAT proofs");
+ }
+ }
+
+ Ok(())
+}
+
+/// Writes a proof step in DRAT format
+pub fn write_step<'s>(target: &mut impl Write, step: &'s ProofStep<'s>) -> io::Result<()> {
+ drat_step(step, |add, clause| {
+ if !add {
+ target.write_all(b"d ")?;
+ }
+ write_literals(target, &clause[..])?;
+ Ok(())
+ })
+}
+
+/// Writes a proof step in binary DRAT format
+pub fn write_binary_step<'s>(target: &mut impl Write, step: &'s ProofStep<'s>) -> io::Result<()> {
+ drat_step(step, |add, clause| {
+ if add {
+ target.write_all(b"a")?;
+ } else {
+ target.write_all(b"d")?;
+ }
+ write_binary_literals(target, &clause[..])?;
+ Ok(())
+ })
+}
+
+/// Writes the literals of a clause for a step in a DRAT proof.
+fn write_literals(target: &mut impl Write, literals: &[Lit]) -> io::Result<()> {
+ for &lit in literals {
+ itoa::write(&mut *target, lit.to_dimacs())?;
+ target.write_all(b" ")?;
+ }
+ target.write_all(b"0\n")?;
+ Ok(())
+}
+
+/// Writes the literals of a clause for a step in a binary DRAT proof.
+fn write_binary_literals(target: &mut impl Write, literals: &[Lit]) -> io::Result<()> {
+ for &lit in literals {
+ let drat_code = lit.code() as u64 + 2;
+ leb128::write::unsigned(target, drat_code)?;
+ }
+ target.write_all(&[0])?;
+ Ok(())
+}
diff --git a/vendor/varisat/src/proof/map_step.rs b/vendor/varisat/src/proof/map_step.rs
new file mode 100644
index 000000000..0c08e6510
--- /dev/null
+++ b/vendor/varisat/src/proof/map_step.rs
@@ -0,0 +1,128 @@
+//! Maps literals and hashes of clause steps between the solver and the checker.
+
+use varisat_formula::{Lit, Var};
+
+use super::{ClauseHash, ProofStep};
+
+/// Maps literals and hashes of clause steps between the solver and the checker.
+#[derive(Default)]
+pub struct MapStep {
+ lit_buf: Vec<Lit>,
+ hash_buf: Vec<ClauseHash>,
+ unit_buf: Vec<(Lit, ClauseHash)>,
+}
+
+impl MapStep {
+ pub fn map_lits(&mut self, lits: &[Lit], map_var: impl Fn(Var) -> Var) -> &[Lit] {
+ let map_var_ref = &map_var;
+ self.lit_buf.clear();
+ self.lit_buf
+ .extend(lits.iter().map(|lit| lit.map_var(map_var_ref)));
+ &self.lit_buf
+ }
+
+ pub fn map<'s, 'a, 'b>(
+ &'a mut self,
+ step: &ProofStep<'b>,
+ map_var: impl Fn(Var) -> Var,
+ map_hash: impl Fn(ClauseHash) -> ClauseHash,
+ ) -> ProofStep<'s>
+ where
+ 'a: 's,
+ 'b: 's,
+ {
+ let map_var_ref = &map_var;
+ let map_lit = |lit: Lit| lit.map_var(map_var_ref);
+ match *step {
+ ProofStep::AddClause { clause } => {
+ self.lit_buf.clear();
+ self.lit_buf.extend(clause.iter().cloned().map(map_lit));
+ ProofStep::AddClause {
+ clause: &self.lit_buf,
+ }
+ }
+
+ ProofStep::AtClause {
+ redundant,
+ clause,
+ propagation_hashes,
+ } => {
+ self.lit_buf.clear();
+ self.lit_buf.extend(clause.iter().cloned().map(map_lit));
+ self.hash_buf.clear();
+ self.hash_buf
+ .extend(propagation_hashes.iter().cloned().map(map_hash));
+ ProofStep::AtClause {
+ redundant,
+ clause: &self.lit_buf,
+ propagation_hashes: &self.hash_buf,
+ }
+ }
+
+ ProofStep::UnitClauses { units } => {
+ self.unit_buf.clear();
+ self.unit_buf.extend(
+ units
+ .iter()
+ .map(|&(lit, hash)| (map_lit(lit), map_hash(hash))),
+ );
+ ProofStep::UnitClauses {
+ units: &self.unit_buf,
+ }
+ }
+
+ ProofStep::DeleteClause { clause, proof } => {
+ self.lit_buf.clear();
+ self.lit_buf.extend(clause.iter().cloned().map(map_lit));
+ ProofStep::DeleteClause {
+ clause: &self.lit_buf,
+ proof,
+ }
+ }
+
+ ProofStep::Model { assignment } => {
+ self.lit_buf.clear();
+ self.lit_buf.extend(assignment.iter().cloned().map(map_lit));
+ ProofStep::Model {
+ assignment: &self.lit_buf,
+ }
+ }
+
+ ProofStep::Assumptions { assumptions } => {
+ self.lit_buf.clear();
+ self.lit_buf
+ .extend(assumptions.iter().cloned().map(map_lit));
+ ProofStep::Assumptions {
+ assumptions: &self.lit_buf,
+ }
+ }
+
+ ProofStep::FailedAssumptions {
+ failed_core,
+ propagation_hashes,
+ } => {
+ self.lit_buf.clear();
+ self.lit_buf
+ .extend(failed_core.iter().cloned().map(map_lit));
+ self.hash_buf.clear();
+ self.hash_buf
+ .extend(propagation_hashes.iter().cloned().map(map_hash));
+ ProofStep::FailedAssumptions {
+ failed_core: &self.lit_buf,
+ propagation_hashes: &self.hash_buf,
+ }
+ }
+
+ ProofStep::ChangeHashBits { .. } | ProofStep::End => *step,
+
+ ProofStep::SolverVarName { .. }
+ | ProofStep::UserVarName { .. }
+ | ProofStep::DeleteVar { .. }
+ | ProofStep::ChangeSamplingMode { .. } => {
+ // while these steps do contain variables, they are used to update the mapping, so
+ // they shouldn't be mapped themselves.
+ *step
+ }
+ }
+ }
+}
diff --git a/vendor/varisat/src/prop.rs b/vendor/varisat/src/prop.rs
new file mode 100644
index 000000000..517f03be2
--- /dev/null
+++ b/vendor/varisat/src/prop.rs
@@ -0,0 +1,261 @@
+//! 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);
+ }
+ }
+}
diff --git a/vendor/varisat/src/prop/assignment.rs b/vendor/varisat/src/prop/assignment.rs
new file mode 100644
index 000000000..a87f60485
--- /dev/null
+++ b/vendor/varisat/src/prop/assignment.rs
@@ -0,0 +1,237 @@
+//! Partial assignment and backtracking.
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::{lit::LitIdx, Lit, Var};
+
+use crate::{
+ context::{parts::*, Context},
+ decision::make_available,
+};
+
+use super::Reason;
+
+/// Current partial assignment.
+#[derive(Default)]
+pub struct Assignment {
+ assignment: Vec<Option<bool>>,
+ last_value: Vec<bool>,
+}
+
+/// This compares two `Option<bool>` values as bytes. Workaround for bad code generation.
+pub fn fast_option_eq(a: Option<bool>, b: Option<bool>) -> bool {
+ unsafe { std::mem::transmute::<_, u8>(a) == std::mem::transmute::<_, u8>(b) }
+}
+
+impl Assignment {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.assignment.resize(count, None);
+ self.last_value.resize(count, false);
+ }
+
+ /// Current partial assignment as slice.
+ pub fn assignment(&self) -> &[Option<bool>] {
+ &self.assignment
+ }
+
+ /// Value assigned to a variable.
+ pub fn var_value(&self, var: Var) -> Option<bool> {
+ self.assignment[var.index()]
+ }
+
+ /// Value last assigned to a variable.
+ ///
+ /// If the variable is currently assigned this returns the previously assigned value. If the
+ /// variable was never assigned this returns false.
+ pub fn last_var_value(&self, var: Var) -> bool {
+ self.last_value[var.index()]
+ }
+
+ /// Value assigned to a literal.
+ pub fn lit_value(&self, lit: Lit) -> Option<bool> {
+ self.assignment[lit.index()].map(|b| b ^ lit.is_negative())
+ }
+
+ pub fn lit_is_true(&self, lit: Lit) -> bool {
+ fast_option_eq(self.assignment[lit.index()], Some(lit.is_positive()))
+ }
+
+ pub fn lit_is_false(&self, lit: Lit) -> bool {
+ fast_option_eq(self.assignment[lit.index()], Some(lit.is_negative()))
+ }
+
+ pub fn lit_is_unk(&self, lit: Lit) -> bool {
+ fast_option_eq(self.assignment[lit.index()], None)
+ }
+
+ pub fn assign_lit(&mut self, lit: Lit) {
+ self.assignment[lit.index()] = lit.is_positive().into()
+ }
+
+ pub fn unassign_var(&mut self, var: Var) {
+ self.assignment[var.index()] = None;
+ }
+
+ /// Set the assigned/unassigned value of a variable.
+ pub fn set_var(&mut self, var: Var, assignment: Option<bool>) {
+ self.assignment[var.index()] = assignment;
+ }
+}
+
+/// Decision and propagation history.
+#[derive(Default)]
+pub struct Trail {
+ /// Stack of all propagated and all enqueued assignments
+ trail: Vec<Lit>,
+ /// Next assignment in trail to propagate
+ queue_head_pos: usize,
+ /// Decision levels as trail indices.
+ decisions: Vec<LitIdx>,
+ /// Number of unit clauses removed from the trail.
+ units_removed: usize,
+}
+
+impl Trail {
+ /// Return the next assigned literal to propagate.
+ pub fn queue_head(&self) -> Option<Lit> {
+ self.trail.get(self.queue_head_pos).cloned()
+ }
+
+ /// Return the next assigned literal to propagate and remove it from the queue.
+ pub fn pop_queue(&mut self) -> Option<Lit> {
+ let head = self.queue_head();
+ if head.is_some() {
+ self.queue_head_pos += 1;
+ }
+ head
+ }
+
+ /// Re-enqueue all assigned literals.
+ pub fn reset_queue(&mut self) {
+ self.queue_head_pos = 0;
+ }
+
+ /// Assigned literals in assignment order.
+ pub fn trail(&self) -> &[Lit] {
+ &self.trail
+ }
+
+ /// Clear the trail.
+ ///
+ /// This simply removes all entries without performing any backtracking. Can only be called with
+ /// no active decisions.
+ pub fn clear(&mut self) {
+ assert!(self.decisions.is_empty());
+ self.units_removed += self.trail.len();
+ self.trail.clear();
+ self.queue_head_pos = 0;
+ }
+
+ /// Start a new decision level.
+ ///
+ /// Does not enqueue the decision itself.
+ pub fn new_decision_level(&mut self) {
+ self.decisions.push(self.trail.len() as LitIdx)
+ }
+
+ /// Current decision level.
+ pub fn current_level(&self) -> usize {
+ self.decisions.len()
+ }
+
+ /// The number of assignments at level 0.
+ pub fn top_level_assignment_count(&self) -> usize {
+ self.decisions
+ .get(0)
+ .map(|&len| len as usize)
+ .unwrap_or(self.trail.len())
+ + self.units_removed
+ }
+
+ /// Whether all assignments are processed.
+ pub fn fully_propagated(&self) -> bool {
+ self.queue_head_pos == self.trail.len()
+ }
+}
+
+/// Enqueues the assignment of true to a literal.
+///
+/// This updates the assignment and trail, but does not perform any propagation. The literal has to
+/// be unassigned when calling this.
+pub fn enqueue_assignment(
+ mut ctx: partial!(Context, mut AssignmentP, mut ImplGraphP, mut TrailP),
+ lit: Lit,
+ reason: Reason,
+) {
+ let assignment = ctx.part_mut(AssignmentP);
+ debug_assert!(assignment.lit_value(lit) == None);
+
+ assignment.assign_lit(lit);
+
+ let (trail, mut ctx) = ctx.split_part_mut(TrailP);
+
+ trail.trail.push(lit);
+
+ let node = &mut ctx.part_mut(ImplGraphP).nodes[lit.index()];
+ node.reason = reason;
+ node.level = trail.decisions.len() as LitIdx;
+ node.depth = trail.trail.len() as LitIdx;
+}
+
+/// Undo all assignments in decision levels deeper than the given level.
+pub fn backtrack(
+ mut ctx: partial!(Context, mut AssignmentP, mut TrailP, mut VsidsP),
+ level: usize,
+) {
+ let (assignment, mut ctx) = ctx.split_part_mut(AssignmentP);
+ let (trail, mut ctx) = ctx.split_part_mut(TrailP);
+
+ // level can actually be greater than the current level. This happens when restart is called
+ // after a conflict backtracked into the assumptions, but before any assumption was reenqueued.
+ // TODO should we update assumption_levels on backtracking instead of allowing this here?
+ if level >= trail.decisions.len() {
+ return;
+ }
+
+ let new_trail_len = trail.decisions[level] as usize;
+
+ trail.queue_head_pos = new_trail_len;
+ trail.decisions.truncate(level);
+
+ let trail_end = &trail.trail[new_trail_len..];
+ for &lit in trail_end {
+ make_available(ctx.borrow(), lit.var());
+ let var_assignment = &mut assignment.assignment[lit.index()];
+ assignment.last_value[lit.index()] = *var_assignment == Some(true);
+ *var_assignment = None;
+ }
+ trail.trail.truncate(new_trail_len);
+}
+
+/// Undo all decisions and assumptions.
+pub fn full_restart(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut AssumptionsP,
+ mut TrailP,
+ mut VsidsP,
+ ),
+) {
+ ctx.part_mut(AssumptionsP).full_restart();
+ backtrack(ctx.borrow(), 0);
+}
+
+/// Undo all decisions.
+pub fn restart(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut TrailP,
+ mut VsidsP,
+ AssumptionsP
+ ),
+) {
+ let level = ctx.part(AssumptionsP).assumption_levels();
+ backtrack(ctx.borrow(), level);
+}
diff --git a/vendor/varisat/src/prop/binary.rs b/vendor/varisat/src/prop/binary.rs
new file mode 100644
index 000000000..d89347c8a
--- /dev/null
+++ b/vendor/varisat/src/prop/binary.rs
@@ -0,0 +1,36 @@
+//! Propagation of binary clauses.
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+
+use crate::context::{parts::*, Context};
+
+use super::{enqueue_assignment, Conflict, Reason};
+
+/// Propagate all literals implied by the given literal via binary clauses.
+///
+/// On conflict return the binary clause propgating the conflicting assignment.
+pub fn propagate_binary(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut ImplGraphP,
+ mut TrailP,
+ BinaryClausesP,
+ ),
+ lit: Lit,
+) -> Result<(), Conflict> {
+ let (binary_clauses, mut ctx) = ctx.split_part(BinaryClausesP);
+
+ for &implied in binary_clauses.implied(lit) {
+ let assignment = ctx.part(AssignmentP);
+
+ if assignment.lit_is_false(implied) {
+ return Err(Conflict::Binary([implied, !lit]));
+ } else if !assignment.lit_is_true(implied) {
+ enqueue_assignment(ctx.borrow(), implied, Reason::Binary([!lit]));
+ }
+ }
+
+ Ok(())
+}
diff --git a/vendor/varisat/src/prop/graph.rs b/vendor/varisat/src/prop/graph.rs
new file mode 100644
index 000000000..3a0e3a42d
--- /dev/null
+++ b/vendor/varisat/src/prop/graph.rs
@@ -0,0 +1,137 @@
+//! The implication graph.
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::{lit::LitIdx, Lit, Var};
+
+use crate::{
+ clause::ClauseRef,
+ context::{parts::*, Context},
+};
+
+/// Assignments that caused a propagation.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Reason {
+ Unit,
+ Binary([Lit; 1]),
+ Long(ClauseRef),
+}
+
+impl Reason {
+ /// The literals that caused the propagation.
+ pub fn lits<'out, 'a, 'b>(&'a self, ctx: &'b partial!('b Context, ClauseAllocP)) -> &'out [Lit]
+ where
+ 'a: 'out,
+ 'b: 'out,
+ {
+ match self {
+ Reason::Unit => &[],
+ Reason::Binary(lit) => lit,
+ // The propagated literal is always kept at position 0
+ Reason::Long(cref) => &ctx.part(ClauseAllocP).clause(*cref).lits()[1..],
+ }
+ }
+
+ /// True if a unit clause or assumption and not a propagation.
+ pub fn is_unit(&self) -> bool {
+ match self {
+ Reason::Unit => true,
+ _ => false,
+ }
+ }
+}
+
+/// Propagation that resulted in a conflict.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum Conflict {
+ Binary([Lit; 2]),
+ Long(ClauseRef),
+}
+
+impl Conflict {
+ /// The literals that caused the conflict.
+ pub fn lits<'out, 'a, 'b>(&'a self, ctx: &'b partial!('b Context, ClauseAllocP)) -> &'out [Lit]
+ where
+ 'a: 'out,
+ 'b: 'out,
+ {
+ match self {
+ Conflict::Binary(lits) => lits,
+ Conflict::Long(cref) => ctx.part(ClauseAllocP).clause(*cref).lits(),
+ }
+ }
+}
+
+/// Node and incoming edges of the implication graph.
+#[derive(Copy, Clone)]
+pub struct ImplNode {
+ pub reason: Reason,
+ pub level: LitIdx,
+ /// Position in trail when assigned, `LitIdx::max_value()` is used as sentinel for removed
+ /// units.
+ pub depth: LitIdx,
+}
+
+/// The implication graph.
+///
+/// This is a DAG having all assigned variables as nodes. It has unit clauses, assumptions and
+/// decisions as sources. For each propagated assignment it has incomming edges from the literals
+/// whose assignment caused the propagation to happen.
+#[derive(Default)]
+pub struct ImplGraph {
+ /// Contains only valid data for indices of assigned variables.
+ pub nodes: Vec<ImplNode>,
+}
+
+impl ImplGraph {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.nodes.resize(
+ count,
+ ImplNode {
+ reason: Reason::Unit,
+ level: 0,
+ depth: 0,
+ },
+ );
+ }
+
+ /// Get the reason for an assigned variable.
+ ///
+ /// Returns stale data if the variable isn't assigned.
+ pub fn reason(&self, var: Var) -> &Reason {
+ &self.nodes[var.index()].reason
+ }
+
+ /// Get the decision level of an assigned variable.
+ ///
+ /// Returns stale data if the variable isn't assigned.
+ pub fn level(&self, var: Var) -> usize {
+ self.nodes[var.index()].level as usize
+ }
+
+ /// Get the trail depth of an assigned variable.
+ ///
+ /// Returns stale data if the variable isn't assigned.
+ pub fn depth(&self, var: Var) -> usize {
+ self.nodes[var.index()].depth as usize
+ }
+
+ /// Updates the reason for an assigned variable.
+ ///
+ /// Make sure the reason vars are in front of the assigned variable in the trail.
+ pub fn update_reason(&mut self, var: Var, reason: Reason) {
+ self.nodes[var.index()].reason = reason
+ }
+
+ /// Updates the reason and depth of a unit clause removed from the trail.
+ pub fn update_removed_unit(&mut self, var: Var) {
+ let node = &mut self.nodes[var.index()];
+ node.reason = Reason::Unit;
+ node.depth = LitIdx::max_value();
+ }
+
+ pub fn is_removed_unit(&self, var: Var) -> bool {
+ self.nodes[var.index()].depth == LitIdx::max_value()
+ }
+}
diff --git a/vendor/varisat/src/prop/long.rs b/vendor/varisat/src/prop/long.rs
new file mode 100644
index 000000000..8fde47ec7
--- /dev/null
+++ b/vendor/varisat/src/prop/long.rs
@@ -0,0 +1,164 @@
+//! Propagation of long clauses.
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+
+use crate::context::{parts::*, Context};
+
+use super::{assignment::fast_option_eq, enqueue_assignment, Conflict, Reason, Watch};
+
+/// Propagate all literals implied by long clauses watched by the given literal.
+///
+/// On conflict return the clause propgating the conflicting assignment.
+///
+/// See [`prop::watch`](crate::prop::watch) for the invariants that this has to uphold.
+pub fn propagate_long(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut ImplGraphP,
+ mut TrailP,
+ mut WatchlistsP,
+ mut ClauseAllocP,
+ ),
+ lit: Lit,
+) -> Result<(), Conflict> {
+ // The code below is heavily optimized and replaces a much nicer but sadly slower version.
+ // Nevertheless it still performs full bound checks. Therefore this function is safe to call
+ // even when some other code violated invariants of for example the clause db.
+ unsafe {
+ let (watchlists, mut ctx) = ctx.split_part_mut(WatchlistsP);
+ let (alloc, mut ctx) = ctx.split_part_mut(ClauseAllocP);
+
+ let watch_begin;
+ let watch_end;
+ {
+ let watch_list = &mut watchlists.watched_by_mut(lit);
+ watch_begin = watch_list.as_mut_ptr();
+ watch_end = watch_begin.add(watch_list.len());
+ }
+ let mut watch_ptr = watch_begin;
+
+ let false_lit = !lit;
+
+ let mut watch_write = watch_ptr;
+
+ let assignment_limit = ctx.part(AssignmentP).assignment().len();
+ let assignment_ptr = ctx.part(AssignmentP).assignment().as_ptr();
+
+ let is_true = |lit: Lit| {
+ assert!(lit.index() < assignment_limit);
+ fast_option_eq(*assignment_ptr.add(lit.index()), Some(lit.is_positive()))
+ };
+
+ let is_false = |lit: Lit| {
+ assert!(lit.index() < assignment_limit);
+ fast_option_eq(*assignment_ptr.add(lit.index()), Some(lit.is_negative()))
+ };
+
+ 'watchers: while watch_ptr != watch_end {
+ let watch = *watch_ptr;
+ watch_ptr = watch_ptr.add(1);
+
+ // If the blocking literal (which is part of the watched clause) is already true, the
+ // watched clause is satisfied and we don't even have to look at it.
+ if is_true(watch.blocking) {
+ *watch_write = watch;
+ watch_write = watch_write.add(1);
+ continue;
+ }
+
+ let cref = watch.cref;
+
+ // Make sure we can access at least 3 lits
+ alloc.check_bounds(cref, 3);
+
+ let clause_ptr = alloc.lits_ptr_mut_unchecked(cref);
+ let &mut header = alloc.header_unchecked_mut(cref);
+
+ // First we ensure that the literal we're currently propagating is at index 1. This
+ // prepares the literal order for further propagations, as the propagating literal has
+ // to be at index 0. Doing this here also avoids a similar check later should the clause
+ // be satisfied by a non-watched literal, as we can just move it to index 1.
+ let mut first = *clause_ptr.add(0);
+ if first == false_lit {
+ let c1 = *clause_ptr.add(1);
+ first = c1;
+ *clause_ptr.add(0) = c1;
+ *clause_ptr.add(1) = false_lit;
+ }
+
+ // We create a new watch with the other watched literal as blocking literal. This will
+ // either replace the currently processed watch or be added to another literals watch
+ // list.
+ let new_watch = Watch {
+ cref,
+ blocking: first,
+ };
+
+ // If the other watched literal (now the first) isn't the blocking literal, check
+ // whether that one is true. If so nothing else needs to be done.
+ if first != watch.blocking && is_true(first) {
+ *watch_write = new_watch;
+ watch_write = watch_write.add(1);
+ continue;
+ }
+
+ // At this point we try to find a non-false unwatched literal to replace our current
+ // literal as the watched literal.
+
+ let clause_len = header.len();
+ let mut lit_ptr = clause_ptr.add(2);
+ let lit_end = clause_ptr.add(clause_len);
+
+ // Make sure we can access all clause literals.
+ alloc.check_bounds(cref, clause_len);
+
+ while lit_ptr != lit_end {
+ let rest_lit = *lit_ptr;
+ if !is_false(rest_lit) {
+ // We found a non-false literal and make it a watched literal by reordering the
+ // literals and adding the watch to the corresponding watchlist.
+ *clause_ptr.offset(1) = rest_lit;
+ *lit_ptr = false_lit;
+
+ // We're currently using unsafe to modify the watchlist of lit, so make extra
+ // sure we're not aliasing.
+ assert_ne!(!rest_lit, lit);
+ watchlists.add_watch(!rest_lit, new_watch);
+ continue 'watchers;
+ }
+ lit_ptr = lit_ptr.add(1);
+ }
+
+ // We didn't find a non-false unwatched literal, so either we're propagating or we have
+ // a conflict.
+ *watch_write = new_watch;
+ watch_write = watch_write.add(1);
+
+ // If the other watched literal is false we have a conflict.
+ if is_false(first) {
+ // We move all unprocessed watches and resize the currentl watchlist.
+ while watch_ptr != watch_end {
+ *watch_write = *watch_ptr;
+ watch_write = watch_write.add(1);
+ watch_ptr = watch_ptr.add(1);
+ }
+ let out_size = ((watch_write as usize) - (watch_begin as usize))
+ / std::mem::size_of::<Watch>();
+
+ watchlists.watched_by_mut(lit).truncate(out_size as usize);
+
+ return Err(Conflict::Long(cref));
+ }
+
+ // Otherwise we enqueue a new propagation.
+ enqueue_assignment(ctx.borrow(), first, Reason::Long(cref));
+ }
+
+ let out_size =
+ ((watch_write as usize) - (watch_begin as usize)) / std::mem::size_of::<Watch>();
+ watchlists.watched_by_mut(lit).truncate(out_size as usize);
+ }
+ Ok(())
+}
diff --git a/vendor/varisat/src/prop/watch.rs b/vendor/varisat/src/prop/watch.rs
new file mode 100644
index 000000000..095ac86a9
--- /dev/null
+++ b/vendor/varisat/src/prop/watch.rs
@@ -0,0 +1,135 @@
+//! Watchlists to detect clauses that became unit.
+//!
+//! Each (long) clause has always two watches pointing to it. The watches are kept in the watchlists
+//! of two different literals of the clause. Whenever the watches are moved to different literals
+//! the litereals of the clause are permuted so the watched literals are in position 0 and 1.
+//!
+//! When a clause is not unit under the current assignment, the watched literals point at two
+//! non-false literals. When a clause is unit and thus propagating, the true literal is watched and
+//! in position 0, the other watched literal is the one with the largest decision level and kept in
+//! position 1. When a clause becomes satisfied before becoming unit the watches can be kept as they
+//! were.
+//!
+//! When a literal is assigned false that invariant can be invalidated. This can be detected by
+//! scanning the watches of the assigned literal. When the assignment is processed the watches are
+//! moved to restore that invariant. Unless there is a conflict, i.e. a clause with no non-false
+//! literals, this can always be done. This also finds all clauses that became unit. The new unit
+//! clauses are exactly those clauses where no two non-false literals can be found.
+//!
+//! There is no need to update watchlists on backtracking, as unassigning variables cannot
+//! invalidate the invariant.
+//!
+//! See [Section 4.5.1 of the "Handbook of Satisfiability"][handbook-ch4] for more details and
+//! references.
+//!
+//! As a further optimization we use blocking literals. This means that each watch stores a literal
+//! of the clause that is different from the watched literal. It can be the other watched literal or
+//! any unwatched literal. When that literal is true, the clause is already satisfied, meaning that
+//! no watches need to be updated. This can be detected by just looking at the watch, avoiding
+//! access of the clause database. This variant was introduced by [Niklas Sörensson and Niklas Eén
+//! in "MINISAT 2.1 and MINISAT++1.0 — SAT Race 2008 Editions"][minisat-2.1].
+//!
+//! [handbook-ch4]: https://www.satassociation.org/articles/FAIA185-0131.pdf
+//! [minisat-2.1]: https://www.cril.univ-artois.fr/SAT09/solvers/booklet.pdf
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::Lit;
+
+use crate::{
+ clause::{db, ClauseRef},
+ context::{parts::*, Context},
+};
+
+/// A watch on a long clause.
+#[derive(Copy, Clone)]
+pub struct Watch {
+ /// Clause which has the referring lit in position 0 or 1.
+ pub cref: ClauseRef,
+ /// A lit of the clause, different from the referring lit.
+ pub blocking: Lit,
+}
+
+/// Watchlists to detect clauses that became unit.
+pub struct Watchlists {
+ /// Contains only valid data for indices of assigned variables.
+ watches: Vec<Vec<Watch>>,
+ /// Whether watchlists are present
+ enabled: bool,
+}
+
+impl Default for Watchlists {
+ fn default() -> Watchlists {
+ Watchlists {
+ watches: vec![],
+ enabled: true,
+ }
+ }
+}
+
+impl Watchlists {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.watches.resize(count * 2, vec![]);
+ }
+
+ /// Start watching a clause.
+ ///
+ /// `lits` have to be the first two literals of the given clause.
+ pub fn watch_clause(&mut self, cref: ClauseRef, lits: [Lit; 2]) {
+ if !self.enabled {
+ return;
+ }
+
+ for i in 0..2 {
+ let watch = Watch {
+ cref,
+ blocking: lits[i ^ 1],
+ };
+ self.add_watch(!lits[i], watch);
+ }
+ }
+
+ /// Return watches for a given literal.
+ pub fn watched_by_mut(&mut self, lit: Lit) -> &mut Vec<Watch> {
+ &mut self.watches[lit.code()]
+ }
+
+ /// Make a literal watch a clause.
+ pub fn add_watch(&mut self, lit: Lit, watch: Watch) {
+ self.watches[lit.code()].push(watch)
+ }
+
+ /// Are watchlists enabled.
+ pub fn enabled(&self) -> bool {
+ self.enabled
+ }
+
+ /// Clear and disable watchlists.
+ ///
+ /// Actual clearing of the watchlists is done on re-enabling of the watchlists.
+ pub fn disable(&mut self) {
+ self.enabled = false;
+ }
+}
+
+/// Enable and rebuild watchlists.
+pub fn enable_watchlists(mut ctx: partial!(Context, mut WatchlistsP, ClauseAllocP, ClauseDbP)) {
+ let (watchlists, mut ctx) = ctx.split_part_mut(WatchlistsP);
+ if watchlists.enabled {
+ return;
+ }
+
+ for watchlist in watchlists.watches.iter_mut() {
+ watchlist.clear();
+ }
+
+ watchlists.enabled = true;
+
+ let (alloc, mut ctx) = ctx.split_part(ClauseAllocP);
+
+ for cref in db::clauses_iter(&ctx.borrow()) {
+ let lits = alloc.clause(cref).lits();
+ watchlists.watch_clause(cref, [lits[0], lits[1]]);
+ }
+}
diff --git a/vendor/varisat/src/schedule.rs b/vendor/varisat/src/schedule.rs
new file mode 100644
index 000000000..1e1b04e57
--- /dev/null
+++ b/vendor/varisat/src/schedule.rs
@@ -0,0 +1,101 @@
+//! Scheduling of processing and solving steps.
+//!
+//! The current implementation is temporary and will be replaced with something more flexible.
+use log::info;
+
+use partial_ref::{partial, PartialRef};
+
+use crate::{
+ cdcl::conflict_step,
+ clause::{
+ collect_garbage,
+ reduce::{reduce_locals, reduce_mids},
+ Tier,
+ },
+ context::{parts::*, Context},
+ prop::restart,
+ state::SatState,
+};
+
+mod luby;
+
+use luby::LubySequence;
+
+/// Scheduling of processing and solving steps.
+#[derive(Default)]
+pub struct Schedule {
+ conflicts: u64,
+ next_restart: u64,
+ restarts: u64,
+ luby: LubySequence,
+}
+
+/// Perform one step of the schedule.
+pub fn schedule_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 ScheduleP,
+ mut SolverStateP,
+ mut TmpDataP,
+ mut TmpFlagsP,
+ mut TrailP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ SolverConfigP,
+ ),
+) -> bool {
+ let (schedule, mut ctx) = ctx.split_part_mut(ScheduleP);
+ let (config, mut ctx) = ctx.split_part(SolverConfigP);
+
+ if ctx.part(SolverStateP).sat_state != SatState::Unknown
+ || ctx.part(SolverStateP).solver_error.is_some()
+ {
+ false
+ } else {
+ if schedule.conflicts > 0 && schedule.conflicts % 5000 == 0 {
+ let db = ctx.part(ClauseDbP);
+ let units = ctx.part(TrailP).top_level_assignment_count();
+ info!(
+ "confl: {}k rest: {} vars: {} bin: {} irred: {} core: {} mid: {} local: {}",
+ schedule.conflicts / 1000,
+ schedule.restarts,
+ ctx.part(AssignmentP).assignment().len() - units,
+ ctx.part(BinaryClausesP).count(),
+ db.count_by_tier(Tier::Irred),
+ db.count_by_tier(Tier::Core),
+ db.count_by_tier(Tier::Mid),
+ db.count_by_tier(Tier::Local)
+ );
+ }
+
+ if schedule.next_restart == schedule.conflicts {
+ restart(ctx.borrow());
+ schedule.restarts += 1;
+ schedule.next_restart += config.luby_restart_interval_scale * schedule.luby.advance();
+ }
+
+ if schedule.conflicts % config.reduce_locals_interval == 0 {
+ reduce_locals(ctx.borrow());
+ }
+ if schedule.conflicts % config.reduce_mids_interval == 0 {
+ reduce_mids(ctx.borrow());
+ }
+
+ collect_garbage(ctx.borrow());
+
+ conflict_step(ctx.borrow());
+ schedule.conflicts += 1;
+ true
+ }
+}
diff --git a/vendor/varisat/src/schedule/luby.rs b/vendor/varisat/src/schedule/luby.rs
new file mode 100644
index 000000000..9b44f7646
--- /dev/null
+++ b/vendor/varisat/src/schedule/luby.rs
@@ -0,0 +1,52 @@
+//! The reluctant doubling Luby sequence.
+//!
+//! This sequence is [A182105](https://oeis.org/A182105).
+
+/// Infinite iterator yielding the Luby sequence.
+pub struct LubySequence {
+ u: u64,
+ v: u64,
+}
+
+impl Default for LubySequence {
+ fn default() -> LubySequence {
+ LubySequence { u: 1, v: 1 }
+ }
+}
+
+impl LubySequence {
+ /// Yields the next number of hte Luby sequence.
+ pub fn advance(&mut self) -> u64 {
+ let result = self.v;
+
+ // Method by Knuth 2012
+ if (self.u & self.u.wrapping_neg()) == self.v {
+ self.u += 1;
+ self.v = 1;
+ } else {
+ self.v <<= 1;
+ }
+
+ result
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn luby_sequence() {
+ let mut luby = LubySequence::default();
+ let initial_terms: Vec<_> = std::iter::repeat_with(|| luby.advance()).take(64).collect();
+
+ assert_eq!(
+ initial_terms,
+ vec![
+ 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 1, 1, 2, 4, 8, 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 1, 1, 2,
+ 4, 8, 16, 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 1, 1, 2, 4, 8, 1, 1, 2, 1, 1, 2, 4, 1, 1,
+ 2, 1, 1, 2, 4, 8, 16, 32, 1,
+ ]
+ )
+ }
+}
diff --git a/vendor/varisat/src/solver.rs b/vendor/varisat/src/solver.rs
new file mode 100644
index 000000000..d347fafd2
--- /dev/null
+++ b/vendor/varisat/src/solver.rs
@@ -0,0 +1,512 @@
+//! Boolean satisfiability solver.
+use std::io;
+
+use partial_ref::{IntoPartialRef, IntoPartialRefMut, PartialRef};
+
+use anyhow::Error;
+use thiserror::Error;
+
+use varisat_checker::ProofProcessor;
+use varisat_dimacs::DimacsParser;
+use varisat_formula::{CnfFormula, ExtendFormula, Lit, Var};
+
+use crate::{
+ assumptions::set_assumptions,
+ config::SolverConfigUpdate,
+ context::{config_changed, parts::*, Context},
+ load::load_clause,
+ proof,
+ schedule::schedule_step,
+ state::SatState,
+ variables,
+};
+
+pub use crate::proof::ProofFormat;
+
+/// Possible errors while solving a formula.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum SolverError {
+ #[error("The solver was interrupted")]
+ Interrupted,
+ #[error("Error in proof processor: {}", cause)]
+ ProofProcessorError {
+ #[source]
+ cause: Error,
+ },
+ #[error("Error writing proof file: {}", cause)]
+ ProofIoError {
+ #[source]
+ cause: io::Error,
+ },
+}
+
+impl SolverError {
+ /// Whether a Solver instance can be used after producing such an error.
+ pub fn is_recoverable(&self) -> bool {
+ match self {
+ SolverError::Interrupted => true,
+ _ => false,
+ }
+ }
+}
+
+/// A boolean satisfiability solver.
+#[derive(Default)]
+pub struct Solver<'a> {
+ ctx: Box<Context<'a>>,
+}
+
+impl<'a> Solver<'a> {
+ /// Create a new solver.
+ pub fn new() -> Solver<'a> {
+ Solver::default()
+ }
+
+ /// Change the solver configuration.
+ pub fn config(&mut self, config_update: &SolverConfigUpdate) -> Result<(), Error> {
+ config_update.apply(&mut self.ctx.solver_config)?;
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ config_changed(ctx.borrow(), config_update);
+ Ok(())
+ }
+
+ /// Add a formula to the solver.
+ pub fn add_formula(&mut self, formula: &CnfFormula) {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ for clause in formula.iter() {
+ load_clause(ctx.borrow(), clause);
+ }
+ }
+
+ /// Reads and adds a formula in DIMACS CNF format.
+ ///
+ /// Using this avoids creating a temporary [`CnfFormula`].
+ pub fn add_dimacs_cnf(&mut self, input: impl io::Read) -> Result<(), Error> {
+ let parser = DimacsParser::parse_incremental(input, |parser| {
+ self.add_formula(&parser.take_formula());
+ Ok(())
+ })?;
+
+ log::info!(
+ "Parsed formula with {} variables and {} clauses",
+ parser.var_count(),
+ parser.clause_count()
+ );
+
+ Ok(())
+ }
+
+ /// Sets the "witness" sampling mode for a variable.
+ pub fn witness_var(&mut self, var: Var) {
+ // TODO add link to sampling mode section of the manual when written
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ let global = variables::global_from_user(ctx.borrow(), var, false);
+ variables::set_sampling_mode(ctx.borrow(), global, variables::data::SamplingMode::Witness);
+ }
+
+ /// Sets the "sample" sampling mode for a variable.
+ pub fn sample_var(&mut self, var: Var) {
+ // TODO add link to sampling mode section of the manual when written
+ // TODO add warning about constrainig variables that previously were witness variables
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ let global = variables::global_from_user(ctx.borrow(), var, false);
+ variables::set_sampling_mode(ctx.borrow(), global, variables::data::SamplingMode::Sample);
+ }
+
+ /// Hide a variable.
+ ///
+ /// Turns a free variable into an existentially quantified variable. If the passed `Var` is used
+ /// again after this call, it refers to a new variable not the previously hidden variable.
+ pub fn hide_var(&mut self, var: Var) {
+ // TODO add link to sampling mode section of the manual when written
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ let global = variables::global_from_user(ctx.borrow(), var, false);
+ variables::set_sampling_mode(ctx.borrow(), global, variables::data::SamplingMode::Hide);
+ }
+
+ /// Observe solver internal variables.
+ ///
+ /// This turns solver internal variables into witness variables. There is no guarantee that the
+ /// newly visible variables correspond to previously hidden variables.
+ ///
+ /// Returns a list of newly visible variables.
+ pub fn observe_internal_vars(&mut self) -> Vec<Var> {
+ // TODO add link to sampling mode section of the manual when written
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ variables::observe_internal_vars(ctx.borrow())
+ }
+
+ /// Check the satisfiability of the current formula.
+ pub fn solve(&mut self) -> Result<bool, SolverError> {
+ self.ctx.solver_state.solver_invoked = true;
+
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ assert!(
+ !ctx.part_mut(SolverStateP).state_is_invalid,
+ "solve() called after encountering an unrecoverable error"
+ );
+
+ while schedule_step(ctx.borrow()) {}
+
+ proof::solve_finished(ctx.borrow());
+
+ self.check_for_solver_error()?;
+
+ match self.ctx.solver_state.sat_state {
+ SatState::Unknown => Err(SolverError::Interrupted),
+ SatState::Sat => Ok(true),
+ SatState::Unsat | SatState::UnsatUnderAssumptions => Ok(false),
+ }
+ }
+
+ /// Check for asynchronously generated errors.
+ ///
+ /// To avoid threading errors out of deep call stacks, we have a solver_error field in the
+ /// SolverState. This function takes and returns that error if present.
+ fn check_for_solver_error(&mut self) -> Result<(), SolverError> {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ let error = ctx.part_mut(SolverStateP).solver_error.take();
+
+ if let Some(error) = error {
+ if !error.is_recoverable() {
+ ctx.part_mut(SolverStateP).state_is_invalid = true;
+ }
+ Err(error)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Assume given literals for future calls to solve.
+ ///
+ /// This replaces the current set of assumed literals.
+ pub fn assume(&mut self, assumptions: &[Lit]) {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ set_assumptions(ctx.borrow(), assumptions);
+ }
+
+ /// Set of literals that satisfy the formula.
+ pub fn model(&self) -> Option<Vec<Lit>> {
+ let ctx = self.ctx.into_partial_ref();
+ if ctx.part(SolverStateP).sat_state == SatState::Sat {
+ Some(
+ ctx.part(VariablesP)
+ .user_var_iter()
+ .flat_map(|user_var| {
+ let global_var = ctx
+ .part(VariablesP)
+ .global_from_user()
+ .get(user_var)
+ .expect("no existing global var for user var");
+ ctx.part(ModelP).assignment()[global_var.index()]
+ .map(|value| user_var.lit(value))
+ })
+ .collect(),
+ )
+ } else {
+ None
+ }
+ }
+
+ /// Subset of the assumptions that made the formula unsatisfiable.
+ ///
+ /// This is not guaranteed to be minimal and may just return all assumptions every time.
+ pub fn failed_core(&self) -> Option<&[Lit]> {
+ match self.ctx.solver_state.sat_state {
+ SatState::UnsatUnderAssumptions => Some(self.ctx.assumptions.user_failed_core()),
+ SatState::Unsat => Some(&[]),
+ SatState::Unknown | SatState::Sat => None,
+ }
+ }
+
+ /// Generate a proof of unsatisfiability during solving.
+ ///
+ /// This needs to be called before any clauses are added.
+ pub fn write_proof(&mut self, target: impl io::Write + 'a, format: ProofFormat) {
+ assert!(
+ self.ctx.solver_state.formula_is_empty,
+ "called after clauses were added"
+ );
+ self.ctx.proof.write_proof(target, format);
+ }
+
+ /// Stop generating a proof of unsatisfiability.
+ ///
+ /// This also flushes internal buffers and closes the target file.
+ pub fn close_proof(&mut self) -> Result<(), SolverError> {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ proof::close_proof(ctx.borrow());
+ self.check_for_solver_error()
+ }
+
+ /// Generate and check a proof on the fly.
+ ///
+ /// This needs to be called before any clauses are added.
+ pub fn enable_self_checking(&mut self) {
+ assert!(
+ self.ctx.solver_state.formula_is_empty,
+ "called after clauses were added"
+ );
+ self.ctx.proof.begin_checking();
+ }
+
+ /// Generate a proof and process it using a [`ProofProcessor`].
+ ///
+ /// This implicitly enables self checking.
+ ///
+ /// This needs to be called before any clauses are added.
+ pub fn add_proof_processor(&mut self, processor: &'a mut dyn ProofProcessor) {
+ assert!(
+ self.ctx.solver_state.formula_is_empty,
+ "called after clauses were added"
+ );
+ self.ctx.proof.add_processor(processor);
+ }
+}
+
+impl<'a> Drop for Solver<'a> {
+ fn drop(&mut self) {
+ let _ = self.close_proof();
+ }
+}
+
+impl<'a> ExtendFormula for Solver<'a> {
+ /// Add a clause to the solver.
+ fn add_clause(&mut self, clause: &[Lit]) {
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ load_clause(ctx.borrow(), clause);
+ }
+
+ /// Add a new variable to the solver.
+ fn new_var(&mut self) -> Var {
+ self.ctx.solver_state.formula_is_empty = false;
+ let mut ctx = self.ctx.into_partial_ref_mut();
+ variables::new_user_var(ctx.borrow())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use proptest::prelude::*;
+
+ use varisat_checker::{CheckedProofStep, CheckerData};
+ use varisat_formula::{
+ cnf_formula, lits,
+ test::{sat_formula, sgen_unsat_formula},
+ };
+
+ use varisat_dimacs::write_dimacs;
+
+ fn enable_test_schedule(solver: &mut Solver) {
+ let mut config = SolverConfigUpdate::new();
+ config.reduce_locals_interval = Some(150);
+ config.reduce_mids_interval = Some(100);
+
+ solver.config(&config).unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected = "solve() called after encountering an unrecoverable error")]
+ fn error_handling_proof_writing() {
+ let mut output_buffer = [0u8; 4];
+ let mut solver = Solver::new();
+ let proof_output = std::io::Cursor::new(&mut output_buffer[..]);
+
+ solver.write_proof(proof_output, ProofFormat::Varisat);
+
+ solver.add_formula(&cnf_formula![
+ -1, -2, -3; -1, -2, -4; -1, -2, -5; -1, -3, -4; -1, -3, -5; -1, -4, -5; -2, -3, -4;
+ -2, -3, -5; -2, -4, -5; -3, -4, -5; 1, 2, 5; 1, 2, 3; 1, 2, 4; 1, 5, 3; 1, 5, 4;
+ 1, 3, 4; 2, 5, 3; 2, 5, 4; 2, 3, 4; 5, 3, 4;
+ ]);
+
+ let result = solver.solve();
+
+ assert!(match result {
+ Err(SolverError::ProofIoError { .. }) => true,
+ _ => false,
+ });
+
+ let _ = solver.solve();
+ }
+
+ struct FailingProcessor;
+
+ impl ProofProcessor for FailingProcessor {
+ fn process_step(
+ &mut self,
+ _step: &CheckedProofStep,
+ _data: CheckerData,
+ ) -> Result<(), Error> {
+ anyhow::bail!("failing processor");
+ }
+ }
+ #[test]
+ #[should_panic(expected = "solve() called after encountering an unrecoverable error")]
+ fn error_handling_proof_processing() {
+ let mut processor = FailingProcessor;
+
+ let mut solver = Solver::new();
+
+ solver.add_proof_processor(&mut processor);
+
+ solver.add_formula(&cnf_formula![
+ -1, -2, -3; -1, -2, -4; -1, -2, -5; -1, -3, -4; -1, -3, -5; -1, -4, -5; -2, -3, -4;
+ -2, -3, -5; -2, -4, -5; -3, -4, -5; 1, 2, 5; 1, 2, 3; 1, 2, 4; 1, 5, 3; 1, 5, 4;
+ 1, 3, 4; 2, 5, 3; 2, 5, 4; 2, 3, 4; 5, 3, 4;
+ ]);
+
+ let result = solver.solve();
+
+ assert!(match result {
+ Err(SolverError::ProofProcessorError { cause }) => {
+ format!("{}", cause) == "failing processor"
+ }
+ _ => false,
+ });
+
+ let _ = solver.solve();
+ }
+
+ #[test]
+ #[should_panic(expected = "called after clauses were added")]
+ fn write_proof_too_late() {
+ let mut solver = Solver::new();
+ solver.add_clause(&lits![1, 2, 3]);
+ solver.write_proof(std::io::sink(), ProofFormat::Varisat);
+ }
+
+ #[test]
+ #[should_panic(expected = "called after clauses were added")]
+ fn add_proof_processor_too_late() {
+ let mut processor = FailingProcessor;
+
+ let mut solver = Solver::new();
+ solver.add_clause(&lits![1, 2, 3]);
+
+ solver.add_proof_processor(&mut processor);
+ }
+
+ #[test]
+ #[should_panic(expected = "called after clauses were added")]
+ fn enable_self_checking_too_late() {
+ let mut solver = Solver::new();
+ solver.add_clause(&lits![1, 2, 3]);
+
+ solver.enable_self_checking();
+ }
+
+ #[test]
+ fn self_check_duplicated_unit_clauses() {
+ let mut solver = Solver::new();
+
+ solver.enable_self_checking();
+
+ solver.add_formula(&cnf_formula![
+ 4;
+ 4;
+ ]);
+
+ assert_eq!(solver.solve().ok(), Some(true));
+ }
+
+ proptest! {
+ #[test]
+ fn sgen_unsat(
+ formula in sgen_unsat_formula(1..7usize),
+ test_schedule in proptest::bool::ANY,
+ ) {
+ let mut solver = Solver::new();
+
+ solver.add_formula(&formula);
+
+ if test_schedule {
+ enable_test_schedule(&mut solver);
+ }
+
+ prop_assert_eq!(solver.solve().ok(), Some(false));
+ }
+
+ #[test]
+ fn sgen_unsat_checked(
+ formula in sgen_unsat_formula(1..7usize),
+ test_schedule in proptest::bool::ANY,
+ ) {
+ let mut solver = Solver::new();
+
+ solver.enable_self_checking();
+
+ solver.add_formula(&formula);
+
+ if test_schedule {
+ enable_test_schedule(&mut solver);
+ }
+
+ prop_assert_eq!(solver.solve().ok(), Some(false));
+ }
+
+ #[test]
+ fn sat(
+ formula in sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0),
+ test_schedule in proptest::bool::ANY,
+ ) {
+ let mut solver = Solver::new();
+
+ solver.add_formula(&formula);
+
+ if test_schedule {
+ enable_test_schedule(&mut solver);
+ }
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+
+ let model = solver.model().unwrap();
+
+ for clause in formula.iter() {
+ prop_assert!(clause.iter().any(|lit| model.contains(lit)));
+ }
+ }
+
+ #[test]
+ fn sat_via_dimacs(formula in sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0)) {
+ let mut solver = Solver::new();
+
+ let mut dimacs = vec![];
+
+ write_dimacs(&mut dimacs, &formula).unwrap();
+
+ solver.add_dimacs_cnf(&mut &dimacs[..]).unwrap();
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+
+ let model = solver.model().unwrap();
+
+ for clause in formula.iter() {
+ prop_assert!(clause.iter().any(|lit| model.contains(lit)));
+ }
+ }
+
+ #[test]
+ fn sgen_unsat_incremental_clauses(formula in sgen_unsat_formula(1..7usize)) {
+ let mut solver = Solver::new();
+
+ let mut last_state = Some(true);
+
+ for clause in formula.iter() {
+ solver.add_clause(clause);
+
+ let state = solver.solve().ok();
+ if state != last_state {
+ prop_assert_eq!(state, Some(false));
+ prop_assert_eq!(last_state, Some(true));
+ last_state = state;
+ }
+ }
+
+ prop_assert_eq!(last_state, Some(false));
+ }
+ }
+}
diff --git a/vendor/varisat/src/state.rs b/vendor/varisat/src/state.rs
new file mode 100644
index 000000000..4fb636e66
--- /dev/null
+++ b/vendor/varisat/src/state.rs
@@ -0,0 +1,42 @@
+//! Miscellaneous solver state.
+use crate::solver::SolverError;
+
+/// Satisfiability state.
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum SatState {
+ Unknown,
+ Sat,
+ Unsat,
+ UnsatUnderAssumptions,
+}
+
+impl Default for SatState {
+ fn default() -> SatState {
+ SatState::Unknown
+ }
+}
+
+/// Miscellaneous solver state.
+///
+/// Anything larger or any larger group of related state variables should be moved into a separate
+/// part of [`Context`](crate::context::Context).
+pub struct SolverState {
+ pub sat_state: SatState,
+ pub formula_is_empty: bool,
+ /// Whether solve was called at least once.
+ pub solver_invoked: bool,
+ pub state_is_invalid: bool,
+ pub solver_error: Option<SolverError>,
+}
+
+impl Default for SolverState {
+ fn default() -> SolverState {
+ SolverState {
+ sat_state: SatState::Unknown,
+ formula_is_empty: true,
+ solver_invoked: false,
+ state_is_invalid: false,
+ solver_error: None,
+ }
+ }
+}
diff --git a/vendor/varisat/src/tmp.rs b/vendor/varisat/src/tmp.rs
new file mode 100644
index 000000000..a822d7fe8
--- /dev/null
+++ b/vendor/varisat/src/tmp.rs
@@ -0,0 +1,35 @@
+//! Temporary data.
+use varisat_formula::Lit;
+
+/// Temporary data used by various parts of the solver.
+///
+/// Make sure to check any documented invariants when using this. Also make sure to check all
+/// existing users when adding invariants.
+#[derive(Default)]
+pub struct TmpData {
+ pub lits: Vec<Lit>,
+ pub lits_2: Vec<Lit>,
+}
+
+/// Temporary data that is automatically resized.
+///
+/// This contains buffers that are automatically resized when the variable count of the solver
+/// changes. They are also always kept in a clean state, so using them doesn't come with costs
+/// proportional to the number of variables.
+///
+/// Make sure to check any documented invariants when using this. Also make sure to check all
+/// existing users when adding invariants.
+#[derive(Default)]
+pub struct TmpFlags {
+ /// A boolean for each literal.
+ ///
+ /// Reset to all-false, keep size.
+ pub flags: Vec<bool>,
+}
+
+impl TmpFlags {
+ /// Update structures for a new variable count.
+ pub fn set_var_count(&mut self, count: usize) {
+ self.flags.resize(count * 2, false);
+ }
+}
diff --git a/vendor/varisat/src/unit_simplify.rs b/vendor/varisat/src/unit_simplify.rs
new file mode 100644
index 000000000..d70130b58
--- /dev/null
+++ b/vendor/varisat/src/unit_simplify.rs
@@ -0,0 +1,198 @@
+//! Simplification using unit clauses.
+
+use partial_ref::{partial, split_borrow, PartialRef};
+
+use varisat_formula::{Lit, Var};
+use varisat_internal_proof::{clause_hash, lit_hash, DeleteClauseProof, ProofStep};
+
+use crate::{
+ binary::simplify_binary,
+ clause::db::filter_clauses,
+ context::{parts::*, Context},
+ proof,
+ prop::{enqueue_assignment, Reason},
+ variables,
+};
+
+/// Remove satisfied clauses and false literals.
+pub fn prove_units<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TrailP,
+ AssignmentP,
+ ClauseAllocP,
+ VariablesP,
+ ),
+) -> bool {
+ let mut new_unit = false;
+
+ if ctx.part(TrailP).current_level() == 0 {
+ let (impl_graph, mut ctx) = ctx.split_part_mut(ImplGraphP);
+
+ let mut unit_proofs = vec![];
+
+ let (trail, mut ctx) = ctx.split_part_mut(TrailP);
+
+ for &lit in trail.trail() {
+ new_unit = true;
+ let ctx_lits = ctx.borrow();
+ let reason = impl_graph.reason(lit.var());
+ if !reason.is_unit() {
+ let lits = impl_graph.reason(lit.var()).lits(&ctx_lits);
+ let hash = clause_hash(lits) ^ lit_hash(lit);
+
+ unit_proofs.push((lit, hash));
+ }
+
+ impl_graph.update_removed_unit(lit.var());
+ }
+
+ trail.clear();
+
+ if !unit_proofs.is_empty() {
+ proof::add_step(
+ ctx.borrow(),
+ true,
+ &ProofStep::UnitClauses {
+ units: &unit_proofs,
+ },
+ );
+ }
+ }
+
+ new_unit
+}
+
+/// Put a removed unit back onto the trail.
+pub fn resurrect_unit<'a>(
+ mut ctx: partial!(Context<'a>, mut AssignmentP, mut ImplGraphP, mut TrailP),
+ lit: Lit,
+) {
+ if ctx.part(ImplGraphP).is_removed_unit(lit.var()) {
+ debug_assert!(ctx.part(AssignmentP).lit_is_true(lit));
+ ctx.part_mut(AssignmentP).unassign_var(lit.var());
+
+ // Because we always enqueue with Reason::Unit this will not cause a unit clause to be
+ // proven in `prove_units`.
+ enqueue_assignment(ctx.borrow(), lit, Reason::Unit);
+ }
+}
+
+/// Remove satisfied clauses and false literals.
+pub fn unit_simplify<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ClauseAllocP,
+ mut ClauseDbP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut VariablesP,
+ mut WatchlistsP,
+ mut VsidsP,
+ AssumptionsP,
+ ),
+) {
+ simplify_binary(ctx.borrow());
+
+ let (assignment, mut ctx) = ctx.split_part(AssignmentP);
+
+ let mut new_lits = vec![];
+ {
+ split_borrow!(proof_ctx = &(mut ProofP, mut SolverStateP, VariablesP) ctx);
+ let (ctx_2, mut ctx) = ctx.split_borrow();
+
+ filter_clauses(ctx_2, |alloc, cref| {
+ let clause = alloc.clause_mut(cref);
+ let redundant = clause.header().redundant();
+ new_lits.clear();
+ for &lit in clause.lits() {
+ match assignment.lit_value(lit) {
+ None => new_lits.push(lit),
+ Some(true) => {
+ proof::add_step(
+ proof_ctx.borrow(),
+ true,
+ &ProofStep::DeleteClause {
+ clause: clause.lits(),
+ proof: if redundant {
+ DeleteClauseProof::Redundant
+ } else {
+ DeleteClauseProof::Satisfied
+ },
+ },
+ );
+ return false;
+ }
+ Some(false) => (),
+ }
+ }
+ if new_lits.len() < clause.lits().len() {
+ if proof_ctx.part(ProofP).is_active() {
+ let hash = [clause_hash(clause.lits())];
+ proof::add_step(
+ proof_ctx.borrow(),
+ true,
+ &ProofStep::AtClause {
+ redundant: redundant && new_lits.len() > 2,
+ clause: &new_lits,
+ propagation_hashes: &hash[..],
+ },
+ );
+ proof::add_step(
+ proof_ctx.borrow(),
+ true,
+ &ProofStep::DeleteClause {
+ clause: clause.lits(),
+ proof: if redundant {
+ DeleteClauseProof::Redundant
+ } else {
+ DeleteClauseProof::Simplified
+ },
+ },
+ );
+ }
+
+ match new_lits[..] {
+ // Cannot have empty or unit clauses after full propagation. An empty clause would
+ // have been a conflict and a unit clause must be satisfied and thus would have been
+ // dropped above.
+ [] | [_] => unreachable!(),
+ [lit_0, lit_1] => {
+ ctx.part_mut(BinaryClausesP)
+ .add_binary_clause([lit_0, lit_1]);
+ false
+ }
+ ref lits => {
+ clause.lits_mut()[..lits.len()].copy_from_slice(lits);
+ clause.header_mut().set_len(lits.len());
+ true
+ }
+ }
+ } else {
+ true
+ }
+ });
+ }
+
+ // TODO detect vars that became isolated without being having a unit clause
+
+ for (var_index, &value) in assignment.assignment().iter().enumerate() {
+ let var = Var::from_index(var_index);
+ if !ctx.part(VariablesP).solver_var_present(var) {
+ continue;
+ }
+ let var_data = ctx.part_mut(VariablesP).var_data_solver_mut(var);
+ if let Some(value) = value {
+ var_data.unit = Some(value);
+ var_data.isolated = true;
+ }
+ if var_data.isolated && !var_data.assumed {
+ variables::remove_solver_var(ctx.borrow(), var);
+ }
+ }
+}
diff --git a/vendor/varisat/src/variables.rs b/vendor/varisat/src/variables.rs
new file mode 100644
index 000000000..ec997430e
--- /dev/null
+++ b/vendor/varisat/src/variables.rs
@@ -0,0 +1,669 @@
+//! Variable mapping and metadata.
+
+use rustc_hash::FxHashSet as HashSet;
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::{Lit, Var};
+use varisat_internal_proof::ProofStep;
+
+use crate::{
+ context::{parts::*, set_var_count, Context},
+ decision, proof,
+};
+
+pub mod data;
+pub mod var_map;
+
+use data::{SamplingMode, VarData};
+use var_map::{VarBiMap, VarBiMapMut, VarMap};
+
+/// Variable mapping and metadata.
+pub struct Variables {
+ /// Bidirectional mapping from user variables to global variables.
+ ///
+ /// Initially this is the identity mapping. This ensures that in the non-assumptions setting the
+ /// map from used user variables to global variables is the identity. This is a requirement for
+ /// generating proofs in non-native formats. Those proofs are not aware of variable renaming,
+ /// but are restricted to the non-incremental setting, so this works out.
+ ///
+ /// This is also requried for native proofs, as they assume that the mapping during the initial
+ /// load is the identity.
+ global_from_user: VarBiMap,
+ /// Bidirectional mapping from global variables to user variables.
+ ///
+ /// This starts with the empty mapping, so only used variables are allocated.
+ solver_from_global: VarBiMap,
+ /// User variables that were explicitly hidden by the user.
+ user_freelist: HashSet<Var>,
+ /// Global variables that can be recycled without increasing the global_watermark.
+ global_freelist: HashSet<Var>,
+ /// Solver variables that are unused and can be recycled.
+ solver_freelist: HashSet<Var>,
+
+ /// Variable metadata.
+ ///
+ /// Indexed by global variable indices.
+ var_data: Vec<VarData>,
+}
+
+impl Default for Variables {
+ fn default() -> Variables {
+ Variables {
+ global_from_user: VarBiMap::default(),
+ solver_from_global: VarBiMap::default(),
+ user_freelist: Default::default(),
+ global_freelist: Default::default(),
+ solver_freelist: Default::default(),
+
+ var_data: vec![],
+ }
+ }
+}
+
+impl Variables {
+ /// Number of allocated solver variables.
+ pub fn solver_watermark(&self) -> usize {
+ self.global_from_solver().watermark()
+ }
+
+ /// Number of allocated global variables.
+ pub fn global_watermark(&self) -> usize {
+ self.var_data.len()
+ }
+
+ /// Number of allocated global variables.
+ pub fn user_watermark(&self) -> usize {
+ self.global_from_user().watermark()
+ }
+
+ /// Iterator over all user variables that are in use.
+ pub fn user_var_iter<'a>(&'a self) -> impl Iterator<Item = Var> + 'a {
+ let global_from_user = self.global_from_user.fwd();
+ (0..self.global_from_user().watermark())
+ .map(Var::from_index)
+ .filter(move |&user_var| global_from_user.get(user_var).is_some())
+ }
+
+ /// Iterator over all global variables that are in use.
+ pub fn global_var_iter<'a>(&'a self) -> impl Iterator<Item = Var> + 'a {
+ (0..self.global_watermark())
+ .map(Var::from_index)
+ .filter(move |&global_var| !self.var_data[global_var.index()].deleted)
+ }
+
+ /// The user to global mapping.
+ pub fn global_from_user(&self) -> &VarMap {
+ &self.global_from_user.fwd()
+ }
+
+ /// Mutable user to global mapping.
+ pub fn global_from_user_mut(&mut self) -> VarBiMapMut {
+ self.global_from_user.fwd_mut()
+ }
+
+ /// The global to solver mapping.
+ pub fn solver_from_global(&self) -> &VarMap {
+ &self.solver_from_global.fwd()
+ }
+
+ /// Mutable global to solver mapping.
+ pub fn solver_from_global_mut(&mut self) -> VarBiMapMut {
+ self.solver_from_global.fwd_mut()
+ }
+
+ /// The global to user mapping.
+ pub fn user_from_global(&self) -> &VarMap {
+ &self.global_from_user.bwd()
+ }
+
+ /// Mutable global to user mapping.
+ pub fn user_from_global_mut(&mut self) -> VarBiMapMut {
+ self.global_from_user.bwd_mut()
+ }
+
+ /// The solver to global mapping.
+ pub fn global_from_solver(&self) -> &VarMap {
+ &self.solver_from_global.bwd()
+ }
+
+ /// Mutable solver to global mapping.
+ pub fn global_from_solver_mut(&mut self) -> VarBiMapMut {
+ self.solver_from_global.bwd_mut()
+ }
+
+ /// Get an existing solver var for a user var.
+ pub fn existing_solver_from_user(&self, user: Var) -> Var {
+ let global = self
+ .global_from_user()
+ .get(user)
+ .expect("no existing global var for user var");
+ let solver = self
+ .solver_from_global()
+ .get(global)
+ .expect("no existing solver var for global var");
+ solver
+ }
+
+ /// Get an existing user var from a solver var.
+ pub fn existing_user_from_solver(&self, solver: Var) -> Var {
+ let global = self
+ .global_from_solver()
+ .get(solver)
+ .expect("no existing global var for solver var");
+ let user = self
+ .user_from_global()
+ .get(global)
+ .expect("no existing user var for global var");
+ user
+ }
+
+ /// Mutable reference to the var data for a global variable.
+ pub fn var_data_global_mut(&mut self, global: Var) -> &mut VarData {
+ if self.var_data.len() <= global.index() {
+ self.var_data.resize(global.index() + 1, VarData::default());
+ }
+ &mut self.var_data[global.index()]
+ }
+
+ /// Mutable reference to the var data for a solver variable.
+ pub fn var_data_solver_mut(&mut self, solver: Var) -> &mut VarData {
+ let global = self
+ .global_from_solver()
+ .get(solver)
+ .expect("no existing global var for solver var");
+ &mut self.var_data[global.index()]
+ }
+
+ /// Var data for a global variable.
+ pub fn var_data_global(&self, global: Var) -> &VarData {
+ &self.var_data[global.index()]
+ }
+
+ /// Check if a solver var is mapped to a global var
+ pub fn solver_var_present(&self, solver: Var) -> bool {
+ self.global_from_solver().get(solver).is_some()
+ }
+
+ /// Get an unmapped global variable.
+ pub fn next_unmapped_global(&self) -> Var {
+ self.global_freelist
+ .iter()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| Var::from_index(self.global_watermark()))
+ }
+
+ /// Get an unmapped global variable.
+ pub fn next_unmapped_solver(&self) -> Var {
+ self.solver_freelist
+ .iter()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| Var::from_index(self.solver_watermark()))
+ }
+
+ /// Get an unmapped user variable.
+ pub fn next_unmapped_user(&self) -> Var {
+ self.user_freelist
+ .iter()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| Var::from_index(self.user_watermark()))
+ }
+}
+
+/// Maps a user variable into a global variable.
+///
+/// If no matching global variable exists a new global variable is allocated.
+pub fn global_from_user<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+ user: Var,
+ require_sampling: bool,
+) -> Var {
+ let variables = ctx.part_mut(VariablesP);
+
+ if user.index() > variables.user_watermark() {
+ // TODO use a batch proof step for this?
+ for index in variables.user_watermark()..user.index() {
+ global_from_user(ctx.borrow(), Var::from_index(index), false);
+ }
+ }
+
+ let variables = ctx.part_mut(VariablesP);
+
+ match variables.global_from_user().get(user) {
+ Some(global) => {
+ if require_sampling
+ && variables.var_data[global.index()].sampling_mode != SamplingMode::Sample
+ {
+ panic!("witness variables cannot be constrained");
+ }
+ global
+ }
+ None => {
+ // Can we add an identity mapping?
+ let global = match variables.var_data.get(user.index()) {
+ Some(var_data) if var_data.deleted => user,
+ None => user,
+ _ => variables.next_unmapped_global(),
+ };
+
+ *variables.var_data_global_mut(global) = VarData::user_default();
+
+ variables.global_from_user_mut().insert(global, user);
+ variables.global_freelist.remove(&global);
+ variables.user_freelist.remove(&user);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::UserVarName {
+ global,
+ user: Some(user),
+ },
+ );
+
+ global
+ }
+ }
+}
+
+/// Maps an existing global variable to a solver variable.
+///
+/// If no matching solver variable exists a new one is allocated.
+pub fn solver_from_global<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ global: Var,
+) -> Var {
+ let variables = ctx.part_mut(VariablesP);
+
+ debug_assert!(!variables.var_data[global.index()].deleted);
+
+ match variables.solver_from_global().get(global) {
+ Some(solver) => solver,
+ None => {
+ let solver = variables.next_unmapped_solver();
+
+ let old_watermark = variables.global_from_solver().watermark();
+
+ variables.solver_from_global_mut().insert(solver, global);
+ variables.solver_freelist.remove(&solver);
+
+ let new_watermark = variables.global_from_solver().watermark();
+
+ if new_watermark > old_watermark {
+ set_var_count(ctx.borrow(), new_watermark);
+ }
+
+ initialize_solver_var(ctx.borrow(), solver, global);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::SolverVarName {
+ global,
+ solver: Some(solver),
+ },
+ );
+
+ solver
+ }
+ }
+}
+
+/// Maps a user variable to a solver variable.
+///
+/// Allocates global and solver variables as requried.
+pub fn solver_from_user<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ user: Var,
+ require_sampling: bool,
+) -> Var {
+ let global = global_from_user(ctx.borrow(), user, require_sampling);
+ solver_from_global(ctx.borrow(), global)
+}
+
+/// Allocates a currently unused user variable.
+///
+/// This is either a user variable above any user variable used so far, or a user variable that was
+/// previously hidden by the user.
+pub fn new_user_var<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+) -> Var {
+ let variables = ctx.part_mut(VariablesP);
+ let user_var = variables.next_unmapped_user();
+ global_from_user(ctx.borrow(), user_var, false);
+ user_var
+}
+
+/// Maps a slice of user lits to solver lits using [`solver_from_user`].
+pub fn solver_from_user_lits<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ solver_lits: &mut Vec<Lit>,
+ user_lits: &[Lit],
+ require_sampling: bool,
+) {
+ solver_lits.clear();
+ solver_lits.extend(user_lits.iter().map(|user_lit| {
+ user_lit.map_var(|user_var| solver_from_user(ctx.borrow(), user_var, require_sampling))
+ }))
+}
+
+/// Changes the sampling mode of a global variable.
+///
+/// If the mode is changed to hidden, an existing user mapping is automatically removed.
+///
+/// If the mode is changed from hidden, a new user mapping is allocated and the user variable is
+/// returned.
+pub fn set_sampling_mode<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+ global: Var,
+ mode: SamplingMode,
+) -> Option<Var> {
+ let variables = ctx.part_mut(VariablesP);
+
+ let var_data = &mut variables.var_data[global.index()];
+
+ assert!(!var_data.deleted);
+
+ if var_data.assumed {
+ panic!("cannot change sampling mode of assumption variable")
+ }
+
+ let previous_mode = var_data.sampling_mode;
+
+ if previous_mode == mode {
+ return None;
+ }
+
+ var_data.sampling_mode = mode;
+
+ let mut result = None;
+
+ if mode != SamplingMode::Hide {
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::ChangeSamplingMode {
+ var: global,
+ sample: mode == SamplingMode::Sample,
+ },
+ );
+ }
+ let variables = ctx.part_mut(VariablesP);
+
+ if previous_mode == SamplingMode::Hide {
+ let user = variables.next_unmapped_user();
+ variables.user_from_global_mut().insert(user, global);
+ variables.user_freelist.remove(&user);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::UserVarName {
+ global,
+ user: Some(user),
+ },
+ );
+
+ result = Some(user);
+ } else if mode == SamplingMode::Hide {
+ if let Some(user) = variables.user_from_global_mut().remove(global) {
+ variables.user_freelist.insert(user);
+ }
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::UserVarName { global, user: None },
+ );
+
+ delete_global_if_unused(ctx.borrow(), global);
+ }
+
+ result
+}
+
+/// Turns all hidden vars into witness vars and returns them.
+pub fn observe_internal_vars<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+) -> Vec<Var> {
+ let mut result = vec![];
+ let mut variables = ctx.part_mut(VariablesP);
+ for global_index in 0..variables.global_watermark() {
+ let global = Var::from_index(global_index);
+ let var_data = &variables.var_data[global.index()];
+ if !var_data.deleted && var_data.sampling_mode == SamplingMode::Hide {
+ let user = set_sampling_mode(ctx.borrow(), global, SamplingMode::Witness).unwrap();
+ result.push(user);
+ variables = ctx.part_mut(VariablesP);
+ }
+ }
+ result
+}
+
+/// Initialize a newly allocated solver variable
+pub fn initialize_solver_var(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut ImplGraphP,
+ mut VsidsP,
+ VariablesP
+ ),
+ solver: Var,
+ global: Var,
+) {
+ let (variables, mut ctx) = ctx.split_part(VariablesP);
+ let data = &variables.var_data[global.index()];
+
+ // This recovers the state of a variable that has a known value and was already propagated. This
+ // is important so that when new clauses containing this variable are added, load_clause knows
+ // to reenqueue the assignment.
+ ctx.part_mut(AssignmentP).set_var(solver, data.unit);
+ if data.unit.is_some() {
+ ctx.part_mut(ImplGraphP).update_removed_unit(solver);
+ }
+ decision::initialize_var(ctx.borrow(), solver, data.unit.is_none());
+
+ // TODO unhiding beyond unit clauses
+}
+
+/// Remove a solver var.
+///
+/// If the variable is isolated and hidden, the global variable is also removed.
+pub fn remove_solver_var<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP, mut VsidsP),
+ solver: Var,
+) {
+ decision::remove_var(ctx.borrow(), solver);
+
+ let variables = ctx.part_mut(VariablesP);
+
+ let global = variables
+ .global_from_solver_mut()
+ .remove(solver)
+ .expect("no existing global var for solver var");
+
+ variables.solver_freelist.insert(solver);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::SolverVarName {
+ global,
+ solver: None,
+ },
+ );
+
+ delete_global_if_unused(ctx.borrow(), global);
+}
+
+/// Delete a global variable if it is unused
+fn delete_global_if_unused<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+ global: Var,
+) {
+ let variables = ctx.part_mut(VariablesP);
+
+ if variables.user_from_global().get(global).is_some() {
+ return;
+ }
+
+ if variables.solver_from_global().get(global).is_some() {
+ return;
+ }
+
+ let data = &mut variables.var_data[global.index()];
+
+ assert!(data.sampling_mode == SamplingMode::Hide);
+
+ if !data.isolated {
+ return;
+ }
+
+ data.deleted = true;
+
+ proof::add_step(ctx.borrow(), false, &ProofStep::DeleteVar { var: global });
+
+ // TODO deletion of unit clauses isn't supported by most DRAT checkers, needs an extra
+ // variable mapping instead.
+
+ ctx.part_mut(VariablesP).global_freelist.insert(global);
+}
+
+#[cfg(test)]
+mod tests {
+ use proptest::{collection, prelude::*};
+
+ use varisat_formula::{
+ test::{sat_formula, sgen_unsat_formula},
+ ExtendFormula, Var,
+ };
+
+ use crate::solver::Solver;
+
+ #[test]
+ #[should_panic(expected = "cannot change sampling mode of assumption variable")]
+ fn cannot_hide_assumed_vars() {
+ let mut solver = Solver::new();
+
+ let (x, y, z) = solver.new_lits();
+
+ solver.assume(&[x, y, z]);
+ solver.hide_var(x.var());
+ }
+
+ #[test]
+ #[should_panic(expected = "cannot change sampling mode of assumption variable")]
+ fn cannot_witness_assumed_vars() {
+ let mut solver = Solver::new();
+
+ let (x, y, z) = solver.new_lits();
+
+ solver.assume(&[x, y, z]);
+ solver.witness_var(x.var());
+ }
+
+ proptest! {
+ #[test]
+ fn sgen_unsat_hidden_with_sat(
+ unsat_formula in sgen_unsat_formula(1..7usize),
+ sat_formula in sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0),
+ ) {
+ use std::cmp::max;
+
+ let mut solver = Solver::new();
+
+ solver.enable_self_checking();
+
+ let cond = Var::from_index(max(unsat_formula.var_count(), sat_formula.var_count()));
+
+ let mut tmp = vec![];
+
+ for clause in unsat_formula.iter() {
+ tmp.clear();
+ tmp.extend_from_slice(&clause);
+ tmp.push(cond.negative());
+ solver.add_clause(&tmp);
+ }
+
+ for i in 0..unsat_formula.var_count() {
+ solver.hide_var(Var::from_index(i));
+ }
+
+ solver.add_formula(&sat_formula);
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+
+ solver.add_clause(&[cond.positive()]);
+
+ prop_assert_eq!(solver.solve().ok(), Some(false));
+ }
+
+ #[test]
+ fn sgen_sat_many_hidden_observe_internal(
+ sat_formulas in collection::vec(
+ sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0),
+ 1..10,
+ )
+ ) {
+ let mut solver = Solver::new();
+
+ solver.enable_self_checking();
+
+ for formula in sat_formulas {
+ solver.add_formula(&formula);
+
+ let new_vars = solver.observe_internal_vars();
+
+ for i in 0..formula.var_count() {
+ solver.hide_var(Var::from_index(i));
+ }
+
+ for var in new_vars {
+ solver.hide_var(var);
+ }
+ }
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+ }
+ }
+}
diff --git a/vendor/varisat/src/variables/data.rs b/vendor/varisat/src/variables/data.rs
new file mode 100644
index 000000000..ea29e6280
--- /dev/null
+++ b/vendor/varisat/src/variables/data.rs
@@ -0,0 +1,70 @@
+//! Data associated with variables.
+
+/// Variable sampling mode.
+///
+/// This partitions all variables into three sets. Using these partitions it is possible to specify
+/// equivalence vs. equisatisfiability per variable. Let V be the set of all variables, S, W and H
+/// the sets of Sampling, Witness and Hidden variables. Let F be the input formula and G be the
+/// current formula. The following invariants are upheld:
+///
+/// * The formulas are always equisatisfiable:
+/// (∃ V) G ⇔ (∃ V) F
+/// * Restricted to the sampling variables they are equivalent:
+/// (∀ S) ((∃ V∖S) G ⇔ (∃ V∖S) F)
+/// * Restricted to the non-hidden variables the input formula is implied:
+/// (∀ V∖H) ((∃ H) G ⇒ (∃ H) F)
+///
+/// This ensures that the solver will be able to find and extend each satisfiable assignment of the
+/// sampling variables to an assignment that covers the witness variables.
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum SamplingMode {
+ Sample,
+ Witness,
+ Hide,
+}
+
+/// Data associated with variables.
+///
+/// This is available for each _global_ variable, even if eliminated within the solver.
+#[derive(Clone)]
+pub struct VarData {
+ /// The variable's sampling mode.
+ pub sampling_mode: SamplingMode,
+ /// Whether the variable is forced by a unit clause.
+ ///
+ /// This is used to remember unit clauses after they are removed from the solver.
+ pub unit: Option<bool>,
+ /// True if there are no clauses containing this variable and other variables.
+ ///
+ /// This is the case if there are no clauses containing this variable or just a unit clause with
+ /// this variable.
+ pub isolated: bool,
+ /// True if this variable is part of the current assumptions.
+ pub assumed: bool,
+ /// Whether the global variable was deleted.
+ pub deleted: bool,
+}
+
+impl Default for VarData {
+ fn default() -> VarData {
+ VarData {
+ sampling_mode: SamplingMode::Hide,
+ unit: None,
+ isolated: true,
+ assumed: false,
+ deleted: true,
+ }
+ }
+}
+
+impl VarData {
+ /// Default variable data for a new user variable.
+ pub fn user_default() -> VarData {
+ VarData {
+ sampling_mode: SamplingMode::Sample,
+ deleted: false,
+ ..VarData::default()
+ }
+ }
+}
diff --git a/vendor/varisat/src/variables/var_map.rs b/vendor/varisat/src/variables/var_map.rs
new file mode 100644
index 000000000..730a6bf52
--- /dev/null
+++ b/vendor/varisat/src/variables/var_map.rs
@@ -0,0 +1,124 @@
+//! Mappings between variable names
+
+use varisat_formula::{lit::LitIdx, Var};
+
+const NO_VAR_IDX: LitIdx = Var::max_count() as LitIdx;
+
+/// A mapping from variables to variables.
+#[derive(Default)]
+pub struct VarMap {
+ mapping: Vec<LitIdx>,
+}
+
+impl VarMap {
+ /// Look up a variable in the mapping
+ pub fn get(&self, from: Var) -> Option<Var> {
+ match self.mapping.get(from.index()).cloned() {
+ Some(index) if index == NO_VAR_IDX => None,
+ Some(index) => Some(Var::from_index(index as usize)),
+ None => None,
+ }
+ }
+
+ /// Insert a new mapping.
+ ///
+ /// Note that the parameters are reversed from the usual order, to match the naming convention
+ /// used for maps.
+ ///
+ /// This has the precondition that `from` is not mapped.
+ pub fn insert(&mut self, into: Var, from: Var) {
+ if self.mapping.len() <= from.index() {
+ self.mapping.resize(from.index() + 1, NO_VAR_IDX);
+ }
+ debug_assert_eq!(self.mapping[from.index()], NO_VAR_IDX);
+ self.mapping[from.index()] = into.index() as LitIdx;
+ }
+
+ /// Remove a mapping.
+ ///
+ /// Does nothing if `from` is not mapped.
+ pub fn remove(&mut self, from: Var) {
+ if self.mapping.len() > from.index() {
+ self.mapping[from.index()] = NO_VAR_IDX;
+ }
+ }
+
+ /// One above the largest index that is mapped.
+ pub fn watermark(&self) -> usize {
+ self.mapping.len()
+ }
+}
+
+/// A bidirectional mapping between variables.
+///
+/// This is initialized with the identity mapping over all variables. It is possible to remove
+/// variables from this mapping on both sides.
+#[derive(Default)]
+pub struct VarBiMap {
+ fwd: VarMap,
+ bwd: VarMap,
+}
+
+impl VarBiMap {
+ /// Access the forward mapping.
+ pub fn fwd(&self) -> &VarMap {
+ &self.fwd
+ }
+
+ /// Access the backward mapping.
+ pub fn bwd(&self) -> &VarMap {
+ &self.bwd
+ }
+
+ /// Mutate the mapping in forward direction.
+ pub fn fwd_mut(&mut self) -> VarBiMapMut {
+ VarBiMapMut {
+ fwd: &mut self.fwd,
+ bwd: &mut self.bwd,
+ }
+ }
+
+ /// Mutate the mapping in backward direction.
+ pub fn bwd_mut(&mut self) -> VarBiMapMut {
+ VarBiMapMut {
+ fwd: &mut self.bwd,
+ bwd: &mut self.fwd,
+ }
+ }
+}
+
+/// Mutable view of a [`VarBiMap`].
+///
+/// Helper so `VarBiMap` mutating routines can work in both directions.
+pub struct VarBiMapMut<'a> {
+ fwd: &'a mut VarMap,
+ bwd: &'a mut VarMap,
+}
+
+impl<'a> VarBiMapMut<'a> {
+ /// Insert a new mapping.
+ ///
+ /// Note that the parameters are reversed from the usual order, to match the naming convention
+ /// used for maps.
+ ///
+ /// This has the precondition that `into` and `from` are not mapped.
+ pub fn insert(&mut self, into: Var, from: Var) {
+ self.fwd.insert(into, from);
+ self.bwd.insert(from, into);
+ }
+
+ /// Remove a mapping.
+ ///
+ /// Does nothing if `from` is not mapped.
+ ///
+ /// Returns the existing mapping if it was present.
+ pub fn remove(&mut self, from: Var) -> Option<Var> {
+ if let Some(into) = self.fwd.get(from) {
+ self.fwd.remove(from);
+ self.bwd.remove(into);
+ Some(into)
+ } else {
+ None
+ }
+ }
+}