/*++ Copyright (c) 2019 Microsoft Corporation Module Name: sat_binspr.cpp Abstract: Inprocessing step for creating SPR binary clauses. Author: Nikolaj Bjorner, Marijn Heule 2019-4-29 Notes: L = { lit1, lit2 } G := { touched_L(C) | C in F and C intersects with L, and not F|L |-_unit untouch_L(C) } G & ~L is satisfiable ------------ Learn ~lit1 or ~lit2 Marijn's version: L = { lit1, lit2 } alpha = L + units in F|L G := { touched_alpha(C) | C in F and C intersects with L, and not F|L |-_unit untouch_alpha(C) } G & ~L is satisfiable --------------------- Learn ~L Alternative: for p in literals: push(1) propagate(p) candidates = literals \ units for (C or p) in use(p) and candidates != empty: push(1) propagate(~C) if inconsistent(): learn C (subsumes C or p) else: candidates' := C union ~(consequences of propagate(~C)) candidates := candidates' intersect candidates pop(1) for q in candidates: add (~q or ~p) pop(1) The idea is that all clauses using p must satisfy q in C or F|pq |-1 untouched(C) The clauses that contain q are not restricted: We directly create G := (~p or ~q) & q, which is satisfiable Let pqM |= F, we claim then that ~pqM |= F - every clause (C or q) that contains q is satisfied by ~pqM - every clause (C or p) that does not contain q positively, but contains p satisfies F|pq |-1 untouched(C) Therefore pqM |= untouched(C) and therefore already M |= untouched(C) - all other clauses are satisfied by pqM, but contain neither p, nor q, so it is already the case that M satisfies the clause. Alternative: for p in literals: push(1) propagate(p) candidates = {} for (C or p) in use(p): push(1) propagate(~C) if inconsistent(): learn C (subsumes C or p) else: candidates := candidates union C union ~(consequences of propagate(~C)) pop(1) for q in candidates: push(1) propagate(q) incons := true for (C or p) in use(p) and incons: push(1) propagate(~C) incons := inconsistent() pop(1) pop(1) if incons: add (~p or ~q) pop(1) The idea is similar to the previous alternative, but it allows a candidate to not be directly unit derivable in all clauses C or p, but could be a failed literal under the assumption ~C. The motivation for this variant is that it is unlikely that we need failed literals to close both (C_1 or p),..., (C_n or p) for all clauses containing p. Alternative: 1. extract BIG Use BIG to limit cone enumeration 2. for each literal lit: enumerate k1 in cone of lit enumerate lit2 in use(~k1) check if cone of lit2 contains k2 such that lit1 in use(~k2) --*/ #include "sat/sat_binspr.h" #include "sat/sat_solver.h" #include "sat/sat_big.h" namespace sat { struct binspr::report { binspr& m_binspr; stopwatch m_watch; report(binspr& b): m_binspr(b) { m_watch.start(); } ~report() { m_watch.stop(); unsigned nb = m_binspr.m_bin_clauses; IF_VERBOSE(2, verbose_stream() << " (sat-binspr :binary " << nb << m_watch << ")\n"); } }; void binspr::operator()() { s = alloc(solver, m_solver.params(), m_solver.rlimit()); m_solver.pop_to_base_level(); s->copy(m_solver, true); unsigned num = s->num_vars(); m_bin_clauses = 0; report _rep(*this); m_use_list.reset(); m_use_list.reserve(num * 2); for (clause* c : s->m_clauses) { if (!c->frozen() && !c->was_removed()) { for (literal lit : *c) { m_use_list[lit.index()].push_back(c); } } } TRACE("sat", s->display(tout);); algorithm2(); if (!s->inconsistent()) { params_ref p; p.set_uint("sat.max_conflicts", 10000); p.set_bool("sat.binspr", false); s->updt_params(p); s->check(0, nullptr); } if (s->inconsistent()) { s->set_conflict(); } else { s->pop_to_base_level(); for (unsigned i = m_solver.init_trail_size(); i < s->init_trail_size(); ++i) { literal lit = s->trail_literal(i); m_solver.assign(lit, s->get_justification(lit)); } TRACE("sat", tout << "added " << (s->init_trail_size() - m_solver.init_trail_size()) << " units\n";); } } /** F, p |- ~u, F, p, q |- ~v, { p, v } u C in F { q, u } u D in F Then use { p, ~u, q, ~v } as alpha, L = { p, q } for u in ~consequences of p: for (u u D) in use(u): for q in D, unassigned: for v in ~consequences(q) | ({p, v} u C) in use(v): check_spr(p, q, u, v) */ void binspr::algorithm2() { mk_masks(); unsigned num_lits = 2 * s->num_vars(); for (unsigned l_idx = 0; l_idx < num_lits && !s->inconsistent(); ++l_idx) { s->checkpoint(); literal p = to_literal(l_idx); TRACE("sat", tout << "p " << p << " " << s->value(p) << "\n";); if (is_used(p) && s->value(p) == l_undef) { s->push(); s->assign_scoped(p); unsigned sz_p = s->m_trail.size(); s->propagate(false); if (s->inconsistent()) { s->pop(1); s->assign_unit(~p); s->propagate(false); TRACE("sat", s->display(tout << "unit\n");); IF_VERBOSE(0, verbose_stream() << "unit " << (~p) << "\n"); continue; } for (unsigned i = sz_p; !s->inconsistent() && i < s->m_trail.size(); ++i) { literal u = ~s->m_trail[i]; TRACE("sat", tout << "p " << p << " u " << u << "\n";); for (clause* cp : m_use_list[u.index()]) { for (literal q : *cp) { if (s->inconsistent()) break; if (s->value(q) != l_undef) continue; s->push(); s->assign_scoped(q); unsigned sz_q = s->m_trail.size(); s->propagate(false); if (s->inconsistent()) { // learn ~p or ~q s->pop(1); block_binary(p, q, true); s->propagate(false); continue; } bool found = false; for (unsigned j = sz_q; !found && j < s->m_trail.size(); ++j) { literal v = ~s->m_trail[j]; for (clause* cp2 : m_use_list[v.index()]) { if (cp2->contains(p)) { if (check_spr(p, q, u, v)) { found = true; break; } } } } s->pop(1); if (found) { block_binary(p, q, false); s->propagate(false); TRACE("sat", s->display(tout);); } } } } s->pop(1); } } } bool binspr::is_used(literal lit) const { return !m_use_list[lit.index()].empty() || !s->get_wlist(~lit).empty(); } bool binspr::check_spr(literal p, literal q, literal u, literal v) { SASSERT(s->value(p) == l_true); SASSERT(s->value(q) == l_true); SASSERT(s->value(u) == l_false); SASSERT(s->value(v) == l_false); init_g(p, q, u, v); literal lits[4] = { p, q, ~u, ~v }; for (unsigned i = 0; g_is_sat() && i < 4; ++i) { binary_are_unit_implied(lits[i]); clauses_are_unit_implied(lits[i]); } TRACE("sat", tout << p << " " << q << " " << u << " " << v << " " << g_is_sat() << "\n";); return g_is_sat(); } void binspr::binary_are_unit_implied(literal p) { for (watched const& w : s->get_wlist(~p)) { if (!g_is_sat()) { break; } if (!w.is_binary_non_learned_clause()) { continue; } clear_alpha(); VERIFY(touch(p)); literal lit = w.get_literal(); SASSERT(lit != p); if (touch(lit)) { add_touched(); continue; } bool inconsistent = (s->value(lit) == l_true); if (s->value(lit) == l_undef) { s->push(); s->assign_scoped(~lit); s->propagate(false); inconsistent = s->inconsistent(); s->pop(1); } if (!inconsistent) { TRACE("sat", tout << "not implied: " << p << " " << lit << "\n";); m_state = 0; add_touched(); } } } void binspr::clauses_are_unit_implied(literal p) { for (clause* cp : m_use_list[p.index()]) { if (!g_is_sat()) break; clause_is_unit_implied(*cp); } } void binspr::clause_is_unit_implied(clause const& c) { s->push(); clear_alpha(); for (literal lit : c) { if (touch(lit)) { continue; } else if (s->value(lit) == l_true) { s->pop(1); return; } else if (s->value(lit) != l_false) { s->assign_scoped(~lit); } } s->propagate(false); bool inconsistent = s->inconsistent(); s->pop(1); if (!inconsistent) { add_touched(); } } void binspr::block_binary(literal lit1, literal lit2, bool learned) { IF_VERBOSE(2, verbose_stream() << "SPR: " << learned << " " << ~lit1 << " " << ~lit2 << "\n"); TRACE("sat", tout << "SPR: " << learned << " " << ~lit1 << " " << ~lit2 << "\n";); s->mk_clause(~lit1, ~lit2, learned ? sat::status::redundant() : sat::status::asserted()); ++m_bin_clauses; } void binspr::g_add_unit(literal lit1, literal lit2) { if (lit1.var() < lit2.var()) { m_state &= 0x2; } else { m_state &= 0x4; } } void binspr::g_add_binary(literal lit1, literal lit2, bool flip2) { bool flip1 = false; if (lit1.var() > lit2.var()) { std::swap(flip1, flip2); } m_state &= ((flip1?0x5:0xA) | (flip2?0x3:0xC)); } // 0 -> 10 // 1 -> 01 // * -> 11 // 00 -> 1000 // 10 -> 0100 // 01 -> 0010 // 11 -> 0001 // *1 -> 00110011 // *0 -> 11001100 // 0* -> 10101010 // 1* -> 01010101 // **1 -> 00001111 // **0 -> 11110000 /** \brief create masks (lsb is left) i = 0: 1010101010101010 i = 1: 1100110011001100 i = 2: 1111000011110000 */ unsigned binspr::mk_mask(unsigned i) { // variable number i is false. unsigned mask0 = (1 << (1 << i)) - 1; // 2^i bits of ones unsigned pos = 1 << (i+1); // how many bits in mask unsigned mask = mask0; while (pos < 32) { mask |= (mask0 << pos); pos += 1 << (i + 1); } return mask; } void binspr::mk_masks() { for (unsigned i = 0; i < max_lits; ++i) { m_false[i] = mk_mask(i); m_true[i] = m_false[i] << (1 << i); } } /** \brief create Boolean function table corresponding to disjunction of literals */ void binspr::add_touched() { unsigned mask = 0; for (unsigned i = 0; i < 4; ++i) { switch (m_vals[i]) { case l_true: mask |= m_true[i]; break; case l_false: mask |= m_false[i]; break; default: break; } } m_state &= mask; TRACE("sat", { bool_var vars[4]; vars[0] = m_p; vars[1] = m_q; vars[2] = m_u; vars[3] = m_v; tout << "touched: "; for (unsigned i = 0; i < 4; ++i) { switch (m_vals[i]) { case l_true: tout << literal(vars[i], false) << " "; break; case l_false: tout << literal(vars[i], true) << " "; break; default: break; } } display_mask(tout << " ", m_state); }); } void binspr::init_g(literal p, literal q, literal u, literal v) { m_p = p.var(); m_q = q.var(); m_u = u.var(); m_v = v.var(); m_state = ~0; clear_alpha(); VERIFY(touch(~p)); VERIFY(touch(~q)); add_touched(); } void binspr::clear_alpha() { m_vals[0] = m_vals[1] = m_vals[2] = m_vals[3] = l_undef; } bool binspr::touch(literal p) { bool_var v = p.var(); if (v == m_p) m_vals[0] = to_lbool(!p.sign()); else if (v == m_q) m_vals[1] = to_lbool(!p.sign()); else if (v == m_u) m_vals[2] = to_lbool(!p.sign()); else if (v == m_v) m_vals[3] = to_lbool(!p.sign()); else return false; return true; } std::ostream& binspr::display_mask(std::ostream& out, unsigned mask) const { for (unsigned i = 0; i < 4; ++i) { out << m_vals[i] << " "; } out << " - "; for (unsigned i = 0; i < 32; ++i) { out << (0 != (mask & (1 << i)) ? 1 : 0); } return out << "\n"; } }