/*++ Copyright (c) 2014 Microsoft Corporation Module Name: sat_bcd.cpp Abstract: Find candidate equivalent literals based on blocked clause decomposition. Based on LPAR-19 paper by Biere & Heule. Author: Nikolaj Bjorner (nbjorner) 2014-09-27. Revision History: */ #include "util/trace.h" #include "util/bit_vector.h" #include "util/map.h" #include "sat/sat_bcd.h" #include "sat/sat_solver.h" namespace sat { bcd::bcd(solver & s): s(s) {} bcd::~bcd() { cleanup(); } struct bcd::scoped_cleanup { bcd& f; scoped_cleanup(bcd& f):f(f) {} ~scoped_cleanup() { f.cleanup(); } }; struct bcd::report { bcd& f; report(bcd& f):f(f) {} ~report() { IF_VERBOSE(1, verbose_stream() << "Decomposed set " << f.m_L.size() << " rest: " << f.m_R.size() << "\n";); TRACE("sat", tout << "Decomposed set " << f.m_L.size() << "\n"; for (bclause b : f.m_L) tout << b.lit << ": " << *b.cls << "\n"; tout << "Remainder " << f.m_R.size() << "\n"; for (bclause b : f.m_R) tout << b.lit << ": " << *b.cls << "\n";); } }; void bcd::operator()(union_find<>& uf) { scoped_cleanup _sc(*this); report _report(*this); pure_decompose(); post_decompose(); sat_sweep(); extract_partition(uf); } void bcd::operator()(clause_vector& clauses, svector& bins) { scoped_cleanup _sc(*this); report _report(*this); pure_decompose(); post_decompose(); for (bclause bc : m_L) { if (bc.cls->size() == 2) bins.push_back(bin_clause((*bc.cls)[0], (*bc.cls)[1])); else clauses.push_back(bc.cls); } } void bcd::init(use_list& ul) { for (clause* cls : s.clauses()) { if (!cls->was_removed()) { ul.insert(*cls); register_clause(cls); } } bin_clauses bc; s.collect_bin_clauses(bc, false, false); // exclude roots. for (auto b : bc) { literal lits[2] = { b.first, b.second }; clause* cls = s.cls_allocator().mk_clause(2, lits, false); ul.insert(*cls); m_bin_clauses.push_back(cls); register_clause(cls); } TRACE("sat", for (clause* cls : m_clauses) if (cls) tout << *cls << "\n";); } void bcd::register_clause(clause* cls) { m_clauses.setx(cls->id(), cls, nullptr); } void bcd::unregister_clause(clause const& cls) { m_clauses.setx(cls.id(), 0, nullptr); } void bcd::pure_decompose() { use_list ul; ul.init(s.num_vars()); init(ul); // // while F != empty // pick a clause and variable x in clause. // get use list U1 of x and U2 of ~x // assume |U1| >= |U2| // add U1 to clause set. // for (clause* cls : m_clauses) { if (cls) { pure_decompose(ul, (*cls)[s.rand()() % cls->size()]); } } } void bcd::pure_decompose(use_list& ul, literal lit) { svector tmpL, tmpR; pure_decompose(ul, lit, tmpL); pure_decompose(ul, ~lit, tmpR); if (tmpL.size() < tmpR.size()) { std::swap(tmpL, tmpR); } m_L.append(tmpL); m_R.append(tmpR); TRACE("bcd", tout << lit << " " << "pos: " << tmpL.size() << " " << "neg: " << tmpR.size() << "\n";); } void bcd::pure_decompose(use_list& ul, literal lit, svector& clauses) { clause_use_list& uses = ul.get(lit); auto it = uses.mk_iterator(); for (; !it.at_end(); it.next()) { clause& cls = it.curr(); if (m_clauses[cls.id()]) { clauses.push_back(bclause(lit, &cls)); unregister_clause(cls); } } } void bcd::post_decompose() { m_marked.reset(); m_marked.resize(2*s.num_vars(), false); use_list ul; ul.init(s.num_vars()); for (bclause bc : m_L) { ul.insert(*bc.cls); } // cheap pass: add clauses from R in order // such that they are blocked with respect to // predecessors. reset_removed(); unsigned j = 0; for (bclause const & bc : m_R) { literal lit = find_blocked(ul, *bc.cls); if (lit != null_literal) { m_L.push_back(bc); set_removed(*bc.cls); ul.insert(*bc.cls); } else { m_R[j++] = bc; } } m_R.shrink(j); #if 0 // Super expensive pass: add clauses from R as long // as BCE produces the empty set of clauses. j = 0; for (bclause const& bc : m_R) { if (!bce(ul, *bc.cls)) { m_R[j++] = bc; } } m_R.shrink(j); #endif } // Note: replay blocked clause elimination: // Suppose C u { c1 } is blocked. // annotate each clause by blocking literal. // for new clause c2, check if C u { c2 } is blocked. // For each c in C record which literal it is blocked. // (Order the clauses in C by block ordering) // l | c is blocked, // => c2 contains ~l => check if c c2 is blocked // bool bcd::bce(use_list& ul, clause& cls0) { IF_VERBOSE(1, verbose_stream() << "bce " << m_L.size() << " " << m_R.size() << " " << cls0 << "\n";); svector& live_clauses = m_live_clauses; live_clauses.reset(); ul.insert(cls0); m_new_L.reset(); m_new_L.push_back(bclause(cls0[0], &cls0)); bool removed = false; reset_removed(); for (bclause bc : m_L) { literal lit = find_blocked(ul, *bc.cls); if (lit == null_literal) { live_clauses.push_back(bc); } else { set_removed(*bc.cls); removed = true; m_new_L.push_back(bclause(lit, bc.cls)); } } while (removed) { removed = false; unsigned j = 0; for (bclause bc : live_clauses) { literal lit = find_blocked(ul, *bc.cls); if (lit == null_literal) { live_clauses[j++] = bc; } else { set_removed(*bc.cls); removed = true; m_new_L.push_back(bclause(lit, bc.cls)); } } live_clauses.shrink(j); } if (live_clauses.empty()) { m_L.reset(); m_L.append(m_new_L); } else { ul.erase(cls0); } return live_clauses.empty(); } literal bcd::find_blocked(use_list& ul, clause const& cls) { TRACE("bcd", tout << cls << "\n";); for (literal lit : cls) { m_marked[(~lit).index()] = true; } literal result = null_literal; for (literal lit : cls) { if (is_blocked(ul, lit)) { TRACE("bcd", tout << "is blocked " << lit << " : " << cls << "\n";); result = lit; break; } } for (literal lit : cls) { m_marked[(~lit).index()] = false; } return result; } bool bcd::is_blocked(use_list& ul, literal lit) const { auto has_blocked_lit = [&,this](clause const& c) { for (literal lit2 : c) if (m_marked[lit2.index()] && ~lit != lit2) { return true; } return false; }; auto it = ul.get(~lit).mk_iterator(); for (; !it.at_end(); it.next()) { clause & cls = it.curr(); if (!is_removed(cls) && !has_blocked_lit(cls)) return false; } return true; } void bcd::init_rbits() { m_rbits.reset(); for (unsigned i = 0; i < s.num_vars(); ++i) { uint64_t lo = s.rand()() + (s.rand()() << 16ull); uint64_t hi = s.rand()() + (s.rand()() << 16ull); m_rbits.push_back(lo + (hi << 32ull)); } } uint64_t bcd::eval_clause(clause const& cls) const { uint64_t b = 0ull; for (literal lit : cls) { b |= lit.sign() ? ~m_rbits[lit.var()] : m_rbits[lit.var()]; } return b; } void bcd::sat_sweep() { init_rbits(); m_L.reverse(); for (bclause& bc : m_L) { uint64_t b = eval_clause(*bc.cls); // v = 0, b = 0 => v := 1 // v = 0, b = 1 => v := 0 // v = 1, b = 0 => v := 0 // v = 1, b = 1 => v := 1 m_rbits[bc.lit.var()] ^= ~b; if (0 != ~b) IF_VERBOSE(0, verbose_stream() << "fix " << bc.lit << " " << *bc.cls << "\n"); VERIFY(0 == ~eval_clause(*bc.cls)); } DEBUG_CODE(verify_sweep();); } void bcd::verify_sweep() { for (bclause const& bc : m_L) { VERIFY(0 == ~eval_clause(*bc.cls)); } } void bcd::extract_partition(union_find<>& uf) { u64_map table; for (unsigned i = 2 * s.num_vars(); i-- > 0; ) { uf.mk_var(); } unsigned num_merge = 0; for (unsigned i = 0; i < s.num_vars(); ++i) { uint64_t val = m_rbits[i]; literal lit(i, false); unsigned index = UINT_MAX; if (s.was_eliminated(lit) || s.value(lit) != l_undef) { // skip } else if (table.find(val, index)) { IF_VERBOSE(0, verbose_stream() << "merge " << val << " " << lit << " " << to_literal(index) << "\n"); uf.merge(lit.index(), index); ++num_merge; } else if (table.find(~val, index)) { IF_VERBOSE(0, verbose_stream() << "merge " << val << " " << (~lit) << " " << to_literal(index) << "\n"); uf.merge((~lit).index(), index); ++num_merge; } else { table.insert(val, lit.index()); } } IF_VERBOSE(0, verbose_stream() << "num merge: " << num_merge << "\n"); TRACE("sat", uf.display(tout);); } void bcd::cleanup() { s.del_clauses(m_bin_clauses); m_bin_clauses.reset(); m_clauses.reset(); m_L.reset(); m_R.reset(); } };