summaryrefslogtreecommitdiffstats
path: root/vendor/varisat/src/analyze_conflict.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat/src/analyze_conflict.rs')
-rw-r--r--vendor/varisat/src/analyze_conflict.rs368
1 files changed, 368 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();
+ }
+}