/*++ Copyright (c) 2011 Microsoft Corporation Module Name: sat_asymm_branch.cpp Abstract: SAT solver asymmetric branching Author: Leonardo de Moura (leonardo) 2011-05-30. Revision History: --*/ #include "sat/sat_asymm_branch.h" #include "sat/sat_asymm_branch_params.hpp" #include "sat/sat_solver.h" #include "sat/sat_big.h" #include "util/stopwatch.h" #include "util/trace.h" namespace sat { asymm_branch::asymm_branch(solver & _s, params_ref const & p): s(_s), m_params(p), m_counter(0) { updt_params(p); reset_statistics(); m_calls = 0; m_touch_index = 0; } struct clause_size_lt { bool operator()(clause * c1, clause * c2) const { return c1->size() > c2->size(); } }; struct asymm_branch::report { asymm_branch & m_asymm_branch; stopwatch m_watch; unsigned m_elim_literals; unsigned m_elim_learned_literals; unsigned m_tr; unsigned m_units; report(asymm_branch & a): m_asymm_branch(a), m_elim_literals(a.m_elim_literals), m_elim_learned_literals(a.m_elim_learned_literals), m_tr(a.m_tr), m_units(a.s.init_trail_size()) { m_watch.start(); } ~report() { m_watch.stop(); IF_VERBOSE(2, unsigned num_learned = (m_asymm_branch.m_elim_learned_literals - m_elim_learned_literals); unsigned num_total = (m_asymm_branch.m_elim_literals - m_elim_literals); unsigned num_units = (m_asymm_branch.s.init_trail_size() - m_units); unsigned elim_lits = (num_total - num_learned); unsigned tr = (m_asymm_branch.m_tr - m_tr); verbose_stream() << " (sat-asymm-branch"; if (elim_lits > 0) verbose_stream() << " :elim-literals " << elim_lits; if (num_learned > 0) verbose_stream() << " :elim-learned-literals " << num_learned; if (num_units > 0) verbose_stream() << " :units " << num_units; if (tr > 0) verbose_stream() << " :hte " << tr; verbose_stream() << " :cost " << m_asymm_branch.m_counter; verbose_stream() << mem_stat(); verbose_stream() << m_watch << ")\n";); } }; void asymm_branch::process_bin(big& big) { m_tr += big.reduce_tr(s); } bool asymm_branch::process(big& big, bool learned) { unsigned elim0 = m_elim_literals; unsigned eliml0 = m_elim_learned_literals; for (unsigned i = 0; i < m_asymm_branch_rounds; ++i) { unsigned elim = m_elim_literals + m_tr; big.init(s, learned); process(&big, s.m_clauses); process(&big, s.m_learned); process_bin(big); s.propagate(false); if (s.m_inconsistent) break; unsigned num_elim = m_elim_literals + m_tr - elim; IF_VERBOSE(4, verbose_stream() << "(sat-asymm-branch-step :elim " << num_elim << ")\n";); if (num_elim == 0) break; } IF_VERBOSE(4, if (m_elim_learned_literals > eliml0) verbose_stream() << "(sat-asymm-branch :elim " << m_elim_learned_literals - eliml0 << ")\n";); return m_elim_literals > elim0; } bool asymm_branch::process(bool learned) { unsigned eliml0 = m_elim_learned_literals; unsigned elim = m_elim_literals; process(nullptr, s.m_clauses); if (learned) process(nullptr, s.m_learned); s.propagate(false); IF_VERBOSE(4, if (m_elim_learned_literals > eliml0) verbose_stream() << "(sat-asymm-branch :elim " << m_elim_learned_literals - eliml0 << ")\n";); return m_elim_literals > elim; } void asymm_branch::process(big* big, clause_vector& clauses) { int64_t limit = -m_asymm_branch_limit; std::stable_sort(clauses.begin(), clauses.end(), clause_size_lt()); m_counter -= clauses.size(); clause_vector::iterator it = clauses.begin(); clause_vector::iterator it2 = it; clause_vector::iterator end = clauses.end(); try { for (; it != end; ++it) { if (s.inconsistent()) { for (; it != end; ++it, ++it2) { *it2 = *it; } break; } clause & c = *(*it); if (m_counter < limit || s.inconsistent() || c.was_removed()) { *it2 = *it; ++it2; continue; } s.checkpoint(); if (big ? !process_sampled(*big, c) : !process(c)) { continue; // clause was removed } *it2 = *it; ++it2; } clauses.set_end(it2); } catch (solver_exception & ex) { // put m_clauses in a consistent state... for (; it != end; ++it, ++it2) { *it2 = *it; } clauses.set_end(it2); m_counter = -m_counter; throw ex; } } void asymm_branch::operator()(bool force) { ++m_calls; if (m_calls <= m_asymm_branch_delay) return; if (!m_asymm_branch && !m_asymm_branch_all && !m_asymm_branch_sampled) return; s.propagate(false); // must propagate, since it uses s.push() if (s.m_inconsistent) return; if (!force && m_counter > 0) { m_counter /= 100; return; } CASSERT("asymm_branch", s.check_invariant()); TRACE("asymm_branch_detail", s.display(tout);); report rpt(*this); bool_vector saved_phase(s.m_phase); flet _is_probing(s.m_is_probing, true); bool change = true; unsigned counter = 0; while (change && counter < 2) { ++counter; change = false; s.m_touch_index++; if (m_asymm_branch_sampled) { big big(s.m_rand); if (process(big, true)) change = true; } if (m_asymm_branch_sampled) { big big(s.m_rand); if (process(big, false)) change = true; } if (m_asymm_branch) { m_counter = 0; if (process(false)) change = true; m_counter = -m_counter; } m_touch_index = s.m_touch_index; } s.m_phase = saved_phase; m_asymm_branch_limit *= 2; if (m_asymm_branch_limit > UINT_MAX) m_asymm_branch_limit = UINT_MAX; CASSERT("asymm_branch", s.check_invariant()); } /** \brief try asymmetric branching on all literals in clause. */ bool asymm_branch::process_all(clause & c) { scoped_detach scoped_d(s, c); // clause must not be used for propagation unsigned sz = c.size(); SASSERT(sz > 0); unsigned i = 0, new_sz = sz; for (i = sz; i-- > 0; ) { if (flip_literal_at(c, i, new_sz)) return cleanup(scoped_d, c, i, new_sz); } return true; } struct asymm_branch::compare_left { big& s; compare_left(big& s): s(s) {} bool operator()(literal u, literal v) const { return s.get_left(u) < s.get_left(v); } }; bool asymm_branch::is_touched(bool_var v) const { return s.m_touched[v] >= m_touch_index; } void asymm_branch::sort(big& big, clause const& c) { sort(big, c.begin(), c.end()); } void asymm_branch::sort(big& big, literal const* begin, literal const* end) { m_pos.reset(); m_neg.reset(); for (; begin != end; ++begin) { literal l = *begin; m_pos.push_back(l); m_neg.push_back(~l); } compare_left cmp(big); std::sort(m_pos.begin(), m_pos.end(), cmp); std::sort(m_neg.begin(), m_neg.end(), cmp); IF_VERBOSE(100, for (literal l : m_pos) verbose_stream() << big.get_left(l) << " "; verbose_stream() << "\n"; for (literal l : m_neg) verbose_stream() << big.get_left(l) << " "; verbose_stream() << "\n"; ); } bool asymm_branch::uhte(big& big, clause & c) { unsigned pindex = 0, nindex = 0; literal lpos = m_pos[pindex++]; literal lneg = m_neg[nindex++]; while (true) { if (big.get_left(lneg) > big.get_left(lpos)) { if (pindex == m_pos.size()) return false; lpos = m_pos[pindex++]; } else if (big.get_right(lneg) < big.get_right(lpos) || (m_pos.size() == 2 && (lpos == ~lneg || big.get_parent(lpos) == lneg))) { if (nindex == m_neg.size()) return false; lneg = m_neg[nindex++]; } else { return true; } } return false; } void asymm_branch::uhle(big& big) { m_to_delete.reset(); if (m_to_delete.empty()) { int right = big.get_right(m_pos.back()); for (unsigned i = m_pos.size() - 1; i-- > 0; ) { literal lit = m_pos[i]; int right2 = big.get_right(lit); if (right2 > right) { // lit => last, so lit can be deleted m_to_delete.push_back(lit); } else { right = right2; } } } if (m_to_delete.empty()) { int right = big.get_right(m_neg[0]); for (unsigned i = 1; i < m_neg.size(); ++i) { literal lit = m_neg[i]; int right2 = big.get_right(lit); if (right > right2) { // ~first => ~lit m_to_delete.push_back(~lit); } else { right = right2; } } } } bool asymm_branch::uhle(scoped_detach& scoped_d, big& big, clause & c) { uhle(big); if (!m_to_delete.empty()) { unsigned j = 0; for (unsigned i = 0; i < c.size(); ++i) { literal lit = c[i]; switch (s.value(lit)) { case l_true: scoped_d.del_clause(); return false; case l_false: break; default: if (!m_to_delete.contains(lit)) { if (i != j) { std::swap(c[i], c[j]); } j++; } break; } } return re_attach(scoped_d, c, j); } else { return true; } } bool asymm_branch::propagate_literal(clause const& c, literal l) { if (!is_touched(l.var())) { return false; } SASSERT(!s.inconsistent()); TRACE("asymm_branch_detail", tout << "assigning: " << l << "\n";); s.assign_scoped(l); s.propagate_core(false); // must not use propagate(), since check_missed_propagation may fail for c return s.inconsistent(); } bool asymm_branch::flip_literal_at(clause const& c, unsigned flip_index, unsigned& new_sz) { VERIFY(s.m_trail.size() == s.m_qhead); bool found_conflict = false; unsigned i = 0, sz = c.size(); s.push(); for (i = 0; !found_conflict && i < sz; i++) { if (i == flip_index) continue; found_conflict = propagate_literal(c, ~c[i]); } if (!found_conflict) { SASSERT(sz == i); found_conflict = propagate_literal(c, c[flip_index]); } s.pop(1); new_sz = i; return found_conflict; } bool asymm_branch::cleanup(scoped_detach& scoped_d, clause& c, unsigned skip_idx, unsigned new_sz) { unsigned j = 0; for (unsigned i = 0; i < new_sz; i++) { if (skip_idx == i) continue; literal l = c[i]; switch (s.value(l)) { case l_undef: if (i != j) { std::swap(c[i], c[j]); } j++; break; case l_false: break; case l_true: UNREACHABLE(); break; } } new_sz = j; return re_attach(scoped_d, c, new_sz); } bool asymm_branch::re_attach(scoped_detach& scoped_d, clause& c, unsigned new_sz) { VERIFY(s.m_trail.size() == s.m_qhead); unsigned old_sz = c.size(); m_elim_literals += old_sz - new_sz; if (c.is_learned()) { m_elim_learned_literals += old_sz - new_sz; } switch (new_sz) { case 0: s.set_conflict(); return false; case 1: TRACE("asymm_branch", tout << "produced unit clause: " << c[0] << "\n";); s.assign_unit(c[0]); s.propagate_core(false); scoped_d.del_clause(); return false; // check_missed_propagation() may fail, since m_clauses is not in a consistent state. case 2: SASSERT(s.value(c[0]) == l_undef && s.value(c[1]) == l_undef); VERIFY(s.value(c[0]) == l_undef && s.value(c[1]) == l_undef); s.mk_bin_clause(c[0], c[1], c.is_learned()); if (s.m_trail.size() > s.m_qhead) s.propagate_core(false); scoped_d.del_clause(); return false; default: s.shrink(c, old_sz, new_sz); return true; } } bool asymm_branch::process_sampled(big& big, clause & c) { scoped_detach scoped_d(s, c); sort(big, c); if (uhte(big, c)) { // don't touch hidden tautologies. // ATE takes care of them. return true; } return uhle(scoped_d, big, c); } bool asymm_branch::process(clause & c) { TRACE("asymm_branch_detail", tout << "processing: " << c << "\n";); SASSERT(s.scope_lvl() == 0); SASSERT(!s.inconsistent()); unsigned sz = c.size(); SASSERT(sz > 0); unsigned i; // check if the clause is already satisfied for (i = 0; i < sz; i++) { if (s.value(c[i]) == l_true) { s.detach_clause(c); s.del_clause(c); return false; } } m_counter -= c.size(); if (m_asymm_branch_all) return process_all(c); // try asymmetric branching // clause must not be used for propagation scoped_detach scoped_d(s, c); unsigned new_sz = c.size(); unsigned flip_position = m_rand(c.size()); bool found_conflict = flip_literal_at(c, flip_position, new_sz); SASSERT(!s.inconsistent()); SASSERT(s.scope_lvl() == 0); if (!found_conflict) { // clause size can't be reduced. return true; } else { // clause can be reduced return cleanup(scoped_d, c, flip_position, new_sz); } } void asymm_branch::updt_params(params_ref const & _p) { sat_asymm_branch_params p(_p); m_asymm_branch = p.asymm_branch(); m_asymm_branch_rounds = p.asymm_branch_rounds(); m_asymm_branch_delay = p.asymm_branch_delay(); m_asymm_branch_sampled = p.asymm_branch_sampled(); m_asymm_branch_limit = p.asymm_branch_limit(); m_asymm_branch_all = p.asymm_branch_all(); if (m_asymm_branch_limit > UINT_MAX) m_asymm_branch_limit = UINT_MAX; } void asymm_branch::collect_param_descrs(param_descrs & d) { sat_asymm_branch_params::collect_param_descrs(d); } void asymm_branch::collect_statistics(statistics & st) const { st.update("sat elim literals", m_elim_literals); st.update("sat tr", m_tr); } void asymm_branch::reset_statistics() { m_elim_literals = 0; m_elim_learned_literals = 0; m_tr = 0; } };