/*++ Copyright (c) 2006 Microsoft Corporation Module Name: smt_conflict_resolution.cpp Abstract: Author: Leonardo de Moura (leonardo) 2008-02-25. Revision History: --*/ #include "ast/ast_pp.h" #include "ast/ast_ll_pp.h" #include "smt/smt_context.h" #include "smt/smt_conflict_resolution.h" namespace smt { // --------------------------- // // Base class // // --------------------------- conflict_resolution::conflict_resolution(ast_manager & m, context & ctx, dyn_ack_manager & dyn_ack_manager, smt_params const & params, literal_vector const & assigned_literals, vector & watches ): m_manager(m), m_params(params), m_ctx(ctx), m_dyn_ack_manager(dyn_ack_manager), m_assigned_literals(assigned_literals), m_lemma_atoms(m), m_todo_js_qhead(0), m_antecedents(nullptr), m_watches(watches), m_new_proofs(m), m_lemma_proof(m) { } /** \brief Mark all enodes in a 'proof' tree branch starting at n n -> ... -> root */ template void conflict_resolution::mark_enodes_in_trans(enode * n) { SASSERT(n->trans_reaches(n->get_root())); while (n) { if (Set) n->set_mark2(); else n->unset_mark2(); n = n->m_trans.m_target; } } /** \brief Find a common ancestor (anc) of n1 and n2 in the 'proof' tree. The common ancestor is used to produce irredundant transitivity proofs. n1 = a1 = ... = ai = ANC = ... = root n2 = b1 = ... = bj = ANC = ... = root The equalities ANC = ... = root should not be included in the proof of n1 = n2. The irredundant proof for n1 = n2 is: n1 = a1 = ... = ai = ANC = bj = ... = b1 = n2 */ enode * conflict_resolution::find_common_ancestor(enode * n1, enode * n2) { SASSERT(n1->get_root() == n2->get_root()); mark_enodes_in_trans(n1); while (true) { SASSERT(n2); if (n2->is_marked2()) { mark_enodes_in_trans(n1); return n2; } n2 = n2->m_trans.m_target; } } /** \brief Process a eq_justification of lhs and rhs. This method executes a step of eq_justification2literals This method may update m_antecedents, m_todo_js and m_todo_eqs. */ void conflict_resolution::eq_justification2literals(enode * lhs, enode * rhs, eq_justification js) { SASSERT(m_antecedents); TRACE("conflict_detail", ast_manager& m = get_manager(); tout << mk_pp(lhs->get_owner(), m) << " = " << mk_pp(rhs->get_owner(), m); switch (js.get_kind()) { case eq_justification::AXIOM: tout << " axiom\n"; break; case eq_justification::EQUATION: tout << " was asserted\nliteral: "; m_ctx.display_literal(tout, js.get_literal()); tout << "\n"; break; case eq_justification::JUSTIFICATION: tout << " justification\n"; break; case eq_justification::CONGRUENCE: tout << " congruence\n"; break; default: break; }); switch(js.get_kind()) { case eq_justification::AXIOM: break; case eq_justification::EQUATION: m_antecedents->push_back(js.get_literal()); break; case eq_justification::JUSTIFICATION: mark_justification(js.get_justification()); break; case eq_justification::CONGRUENCE: { CTRACE("dyn_ack_target", !lhs->is_eq(), tout << "dyn_ack_target2: " << lhs->get_owner_id() << " " << rhs->get_owner_id() << "\n";); m_dyn_ack_manager.used_cg_eh(lhs->get_owner(), rhs->get_owner()); unsigned num_args = lhs->get_num_args(); SASSERT(num_args == rhs->get_num_args()); if (js.used_commutativity()) { SASSERT(num_args == 2); mark_eq(lhs->get_arg(0), rhs->get_arg(1)); mark_eq(lhs->get_arg(1), rhs->get_arg(0)); } else { for (unsigned i = 0; i < num_args; i++) mark_eq(lhs->get_arg(i), rhs->get_arg(i)); } break; } default: UNREACHABLE(); } } /** \brief Process the transitivity 'proof' from n1 and n2, where n1 and n2 are in the same branch n1 -> ... -> n2 This method may update m_antecedents, m_todo_js and m_todo_eqs. The resultant set of literals is stored in m_antecedents. */ void conflict_resolution::eq_branch2literals(enode * n1, enode * n2) { SASSERT(n1->trans_reaches(n2)); while (n1 != n2) { eq_justification2literals(n1, n1->m_trans.m_target, n1->m_trans.m_justification); n1 = n1->m_trans.m_target; } } /** \brief Process the justification of n1 = n2. This method may update m_antecedents, m_todo_js and m_todo_eqs. The resultant set of literals is stored in m_antecedents. */ void conflict_resolution::eq2literals(enode * n1, enode * n2) { enode * c = find_common_ancestor(n1, n2); eq_branch2literals(n1, c); eq_branch2literals(n2, c); m_dyn_ack_manager.used_eq_eh(n1->get_owner(), n2->get_owner(), c->get_owner()); } /** \brief Extract the antecedent literals from a justification object. The result is stored in result. \remark This method does not reset the vectors m_antecedents, m_todo_js, m_todo_eqs, nor reset the marks in the justification objects. */ void conflict_resolution::justification2literals_core(justification * js, literal_vector & result) { SASSERT(m_todo_js_qhead <= m_todo_js.size()); m_antecedents = &result; mark_justification(js); process_justifications(); } void conflict_resolution::process_justifications() { while (true) { unsigned sz = m_todo_js.size(); while (m_todo_js_qhead < sz) { justification * js = m_todo_js[m_todo_js_qhead]; m_todo_js_qhead++; js->get_antecedents(*this); } while (!m_todo_eqs.empty()) { enode_pair p = m_todo_eqs.back(); m_todo_eqs.pop_back(); eq2literals(p.first, p.second); } if (m_todo_js_qhead == m_todo_js.size()) { m_antecedents = nullptr; return; } } } /** \brief Unset the mark of the justifications stored in m_todo_js */ void conflict_resolution::unmark_justifications(unsigned old_js_qhead) { SASSERT(old_js_qhead <= m_todo_js.size()); justification_vector::iterator it = m_todo_js.begin() + old_js_qhead; justification_vector::iterator end = m_todo_js.end(); for (; it != end; ++it) { (*it)->unset_mark(); } m_todo_js.shrink(old_js_qhead); m_todo_js_qhead = old_js_qhead; m_todo_eqs.reset(); m_already_processed_eqs.reset(); } /** \brief Extract the antecedent literals from a justification object. */ void conflict_resolution::justification2literals(justification * js, literal_vector & result) { SASSERT(m_todo_js.empty()); SASSERT(m_todo_js_qhead == 0); SASSERT(m_todo_eqs.empty()); justification2literals_core(js, result); unmark_justifications(0); SASSERT(m_todo_eqs.empty()); } void conflict_resolution::eq2literals(enode* n1, enode* n2, literal_vector & result) { SASSERT(m_todo_js.empty()); SASSERT(m_todo_js_qhead == 0); SASSERT(m_todo_eqs.empty()); m_antecedents = &result; m_todo_eqs.push_back(enode_pair(n1, n2)); process_justifications(); unmark_justifications(0); SASSERT(m_todo_eqs.empty()); } /** \brief Return maximum scope level of an antecedent literal of js. */ unsigned conflict_resolution::get_justification_max_lvl(justification * js) { unsigned r = 0; literal_vector & antecedents = m_tmp_literal_vector; antecedents.reset(); justification2literals(js, antecedents); for (literal lit : antecedents) r = std::max(r, m_ctx.get_assign_level(lit)); return r; } /** \brief Return the maximum scope level of the antecedent literals of the given justified literal. */ unsigned conflict_resolution::get_max_lvl(literal consequent, b_justification js) { unsigned r = 0; if (consequent != false_literal) r = m_ctx.get_assign_level(consequent); switch (js.get_kind()) { case b_justification::CLAUSE: { clause * cls = js.get_clause(); unsigned num_lits = cls->get_num_literals(); unsigned i = 0; if (consequent != false_literal) { SASSERT((*cls)[0] == consequent || (*cls)[1] == consequent); if ((*cls)[0] == consequent) { i = 1; } else { r = std::max(r, m_ctx.get_assign_level((*cls)[0])); i = 2; } } for(; i < num_lits; i++) r = std::max(r, m_ctx.get_assign_level((*cls)[i])); justification * js = cls->get_justification(); if (js) r = std::max(r, get_justification_max_lvl(js)); break; } case b_justification::BIN_CLAUSE: r = std::max(r, m_ctx.get_assign_level(js.get_literal())); break; case b_justification::AXIOM: break; case b_justification::JUSTIFICATION: r = std::max(r, get_justification_max_lvl(js.get_justification())); break; default: UNREACHABLE(); } return r; } void conflict_resolution::process_antecedent(literal antecedent, unsigned & num_marks) { bool_var var = antecedent.var(); unsigned lvl = m_ctx.get_assign_level(var); SASSERT(var < static_cast(m_ctx.get_num_bool_vars())); TRACE("conflict_", tout << "processing antecedent (level " << lvl << "):"; m_ctx.display_literal(tout, antecedent); m_ctx.display_detailed_literal(tout << " ", antecedent) << "\n";); if (!m_ctx.is_marked(var) && lvl > m_ctx.get_base_level()) { m_ctx.set_mark(var); m_ctx.inc_bvar_activity(var); expr * n = m_ctx.bool_var2expr(var); if (is_app(n)) { family_id fid = to_app(n)->get_family_id(); theory * th = m_ctx.get_theory(fid); if (th) th->conflict_resolution_eh(to_app(n), var); } if (get_manager().has_trace_stream()) { get_manager().trace_stream() << "[resolve-lit] " << m_conflict_lvl - lvl << " "; m_ctx.display_literal(get_manager().trace_stream(), ~antecedent) << "\n"; } if (lvl == m_conflict_lvl) { TRACE("conflict", m_ctx.display_literal(tout << "marking:", antecedent) << "\n";); num_marks++; } else { m_lemma.push_back(~antecedent); m_lemma_atoms.push_back(m_ctx.bool_var2expr(var)); } } } void conflict_resolution::process_justification(justification * js, unsigned & num_marks) { literal_vector & antecedents = m_tmp_literal_vector; antecedents.reset(); justification2literals_core(js, antecedents); for (literal l : antecedents) process_antecedent(l, num_marks); } /** \brief Skip literals from levels above m_conflict_lvl. It returns an index idx such that m_assigned_literals[idx] <= m_conflict_lvl, and for all idx' > idx, m_assigned_literals[idx'] > m_conflict_lvl */ unsigned conflict_resolution::skip_literals_above_conflict_level() { unsigned idx = m_assigned_literals.size(); if (idx == 0) { return idx; } idx--; // skip literals from levels above the conflict level while (m_ctx.get_assign_level(m_assigned_literals[idx]) > m_conflict_lvl) { SASSERT(idx > 0); idx--; } return idx; } /** \brief Initialize conflict resolution data-structures. Return false if the conflict cannot be resolved (it is at the search level). */ bool conflict_resolution::initialize_resolve(b_justification conflict, literal not_l, b_justification & js, literal & consequent) { TRACE("conflict_detail", m_ctx.display(tout);); m_lemma.reset(); m_lemma_atoms.reset(); SASSERT(m_ctx.get_search_level() >= m_ctx.get_base_level()); js = conflict; consequent = false_literal; if (not_l != null_literal) consequent = ~not_l; m_conflict_lvl = get_max_lvl(consequent, js); TRACE("conflict_bug", tout << "conflict_lvl: " << m_conflict_lvl << " scope_lvl: " << m_ctx.get_scope_level() << " base_lvl: " << m_ctx.get_base_level() << " search_lvl: " << m_ctx.get_search_level() << "\n"; tout << "js.kind: " << js.get_kind() << "\n"; tout << "consequent: " << consequent << ": "; m_ctx.display_literal_verbose(tout, consequent) << "\n"; m_ctx.display(tout, js); tout << "\n"; ); // m_conflict_lvl can be smaller than m_ctx.get_search_level() when: // there are user level scopes created using the Z3 API, and // the previous levels were already inconsistent, or the inconsistency was // triggered by an axiom or justification proof wrapper, this two kinds // of justification are considered level zero. if (m_conflict_lvl <= m_ctx.get_search_level()) { TRACE("conflict", tout << "problem is unsat\n";); if (m_manager.proofs_enabled()) mk_conflict_proof(conflict, not_l); if (m_ctx.tracking_assumptions()) mk_unsat_core(conflict, not_l); return false; } TRACE("conflict", tout << "conflict_lvl: " << m_conflict_lvl << "\n";); SASSERT(!m_assigned_literals.empty()); SASSERT(m_todo_js.empty()); SASSERT(m_todo_js_qhead == 0); SASSERT(m_todo_eqs.empty()); return true; } /** \brief Cleanup datastructures used during resolve(), minimize lemma (when minimization is enabled), compute m_new_scope_lvl and m_lemma_iscope_lvl, generate proof if needed. This method assumes that the lemma is stored in m_lemma (and the associated atoms in m_lemma_atoms). \warning This method assumes the literals in m_lemma[1] ... m_lemma[m_lemma.size() - 1] are marked. */ void conflict_resolution::finalize_resolve(b_justification conflict, literal not_l) { unmark_justifications(0); TRACE("conflict", m_ctx.display_literals(tout << "before minimization:\n", m_lemma) << "\n" << m_lemma << "\n";); TRACE("conflict_verbose",m_ctx.display_literals_verbose(tout << "before minimization:\n", m_lemma) << "\n";); if (m_params.m_minimize_lemmas) minimize_lemma(); TRACE("conflict", m_ctx.display_literals(tout << "after minimization:\n", m_lemma) << "\n";); TRACE("conflict_verbose", m_ctx.display_literals_verbose(tout << "after minimization:\n", m_lemma) << "\n";); TRACE("conflict_bug", m_ctx.display_literals_verbose(tout, m_lemma) << "\n";); literal_vector::iterator it = m_lemma.begin(); literal_vector::iterator end = m_lemma.end(); m_new_scope_lvl = m_ctx.get_search_level(); m_lemma_iscope_lvl = m_ctx.get_intern_level((*it).var()); SASSERT(!m_ctx.is_marked((*it).var())); ++it; for(; it != end; ++it) { bool_var var = (*it).var(); if (var != null_bool_var) { m_ctx.unset_mark(var); unsigned lvl = m_ctx.get_assign_level(var); if (lvl > m_new_scope_lvl) m_new_scope_lvl = lvl; lvl = m_ctx.get_intern_level(var); if (lvl > m_lemma_iscope_lvl) m_lemma_iscope_lvl = lvl; } } TRACE("conflict", tout << "new scope level: " << m_new_scope_lvl << "\n"; tout << "intern. scope level: " << m_lemma_iscope_lvl << "\n";); if (m_manager.proofs_enabled()) mk_conflict_proof(conflict, not_l); } bool conflict_resolution::resolve(b_justification conflict, literal not_l) { b_justification js; literal consequent; if (!initialize_resolve(conflict, not_l, js, consequent)) { return false; } unsigned idx = skip_literals_above_conflict_level(); TRACE("conflict", m_ctx.display_literal_verbose(tout, not_l); m_ctx.display(tout << " ", conflict);); // save space for first uip m_lemma.push_back(null_literal); m_lemma_atoms.push_back(nullptr); unsigned num_marks = 0; if (not_l != null_literal) { TRACE("conflict", tout << "not_l: "; m_ctx.display_literal_verbose(tout, not_l) << "\n";); process_antecedent(not_l, num_marks); } do { if (get_manager().has_trace_stream()) { get_manager().trace_stream() << "[resolve-process] "; m_ctx.display_literal(get_manager().trace_stream(), ~consequent); get_manager().trace_stream() << "\n"; } TRACE("conflict", tout << "processing consequent id: " << idx << " lit: " << consequent << " "; m_ctx.display_literal(tout, consequent); m_ctx.display_literal_verbose(tout << " ", consequent) << "\n"; tout << "num_marks: " << num_marks << ", js kind: " << js.get_kind() << " level: " << m_ctx.get_assign_level(consequent) << "\n"; ); SASSERT(js != null_b_justification); switch (js.get_kind()) { case b_justification::CLAUSE: { clause * cls = js.get_clause(); TRACE("conflict", m_ctx.display_clause_detail(tout, cls);); TRACE("conflict", tout << literal_vector(cls->get_num_literals(), cls->begin()) << "\n";); if (cls->is_lemma()) cls->inc_clause_activity(); unsigned num_lits = cls->get_num_literals(); unsigned i = 0; if (consequent != false_literal) { SASSERT((*cls)[0] == consequent || (*cls)[1] == consequent); if ((*cls)[0] == consequent) { i = 1; } else { literal l = (*cls)[0]; SASSERT(consequent.var() != l.var()); process_antecedent(~l, num_marks); i = 2; } } for(; i < num_lits; i++) { literal l = (*cls)[i]; SASSERT(consequent.var() != l.var()); process_antecedent(~l, num_marks); } justification * js = cls->get_justification(); if (js) process_justification(js, num_marks); break; } case b_justification::BIN_CLAUSE: SASSERT(consequent.var() != js.get_literal().var()); process_antecedent(js.get_literal(), num_marks); break; case b_justification::AXIOM: break; case b_justification::JUSTIFICATION: process_justification(js.get_justification(), num_marks); break; default: UNREACHABLE(); } while (true) { literal l = m_assigned_literals[idx]; if (m_ctx.is_marked(l.var())) break; CTRACE("conflict", m_ctx.get_assign_level(l) != m_conflict_lvl && m_ctx.get_assign_level(l) != m_ctx.get_base_level(), tout << "assign_level(l): " << m_ctx.get_assign_level(l) << ", conflict_lvl: "; tout << m_conflict_lvl << ", l: "; m_ctx.display_literal_verbose(tout, l); tout << "\n"; tout << "num marks: " << num_marks << "\n";); SASSERT(m_ctx.get_assign_level(l) == m_conflict_lvl || // it may also be an (out-of-order) asserted literal m_ctx.get_assign_level(l) == m_ctx.get_base_level()); SASSERT(idx > 0); idx--; } consequent = m_assigned_literals[idx]; bool_var c_var = consequent.var(); SASSERT(m_ctx.get_assign_level(c_var) == m_conflict_lvl); js = m_ctx.get_justification(c_var); idx--; num_marks--; m_ctx.unset_mark(c_var); } while (num_marks > 0); TRACE("conflict", tout << "FUIP: "; m_ctx.display_literal(tout, consequent); tout << "\n";); m_lemma[0] = ~consequent; m_lemma_atoms.set(0, m_ctx.bool_var2expr(consequent.var())); // TODO: // // equality optimization should go here. // finalize_resolve(conflict, not_l); return true; } /** \brief Return an approximation for the set of scope levels where the literals in m_lemma were assigned. */ level_approx_set conflict_resolution::get_lemma_approx_level_set() { level_approx_set result; for (literal l : m_lemma) result.insert(m_ctx.get_assign_level(l)); return result; } /** \brief Restore the size of m_unmark to old_size, and unmark literals at positions [old_size, m_unmark.size()). */ void conflict_resolution::reset_unmark(unsigned old_size) { unsigned curr_size = m_unmark.size(); for(unsigned i = old_size; i < curr_size; i++) m_ctx.unset_mark(m_unmark[i]); m_unmark.shrink(old_size); } /** \brief Restore the size of m_unmark to old_size, and unmark literals at positions [old_size, m_unmark.size()). And unmark all justifications at positions [old_js_qhead, m_todo_js.size()). */ void conflict_resolution::reset_unmark_and_justifications(unsigned old_size, unsigned old_js_qhead) { reset_unmark(old_size); unmark_justifications(old_js_qhead); } /** \brief Process an antecedent for lemma minimization. */ bool conflict_resolution::process_antecedent_for_minimization(literal antecedent) { bool_var var = antecedent.var(); unsigned lvl = m_ctx.get_assign_level(var); if (!m_ctx.is_marked(var) && lvl > m_ctx.get_base_level()) { if (m_lvl_set.may_contain(lvl)) { m_ctx.set_mark(var); m_unmark.push_back(var); m_lemma_min_stack.push_back(var); } else { return false; } } return true; } bool conflict_resolution::process_justification_for_minimization(justification * js) { literal_vector & antecedents = m_tmp_literal_vector; antecedents.reset(); // Invoking justification2literals_core will not reset the caches for visited justifications and eqs. // The method unmark_justifications must be invoked to reset these caches. // Remark: The method reset_unmark_and_justifications invokes unmark_justifications. justification2literals_core(js, antecedents); for (literal l : antecedents) if (!process_antecedent_for_minimization(l)) return false; return true; } /** \brief Return true if lit is implied by other marked literals and/or literals assigned at the base level. The set lvl_set is used as an optimization. The idea is to stop the recursive search with a failure as soon as we find a literal assigned in a level that is not in lvl_set. */ bool conflict_resolution::implied_by_marked(literal lit) { m_lemma_min_stack.reset(); // avoid recursive function m_lemma_min_stack.push_back(lit.var()); unsigned old_size = m_unmark.size(); unsigned old_js_qhead = m_todo_js_qhead; while (!m_lemma_min_stack.empty()) { bool_var var = m_lemma_min_stack.back(); m_lemma_min_stack.pop_back(); b_justification js = m_ctx.get_justification(var); SASSERT(js != null_b_justification); switch(js.get_kind()) { case b_justification::CLAUSE: { clause * cls = js.get_clause(); unsigned num_lits = cls->get_num_literals(); unsigned pos = (*cls)[1].var() == var; for (unsigned i = 0; i < num_lits; i++) { if (pos != i) { literal l = (*cls)[i]; SASSERT(l.var() != var); if (!process_antecedent_for_minimization(~l)) { reset_unmark_and_justifications(old_size, old_js_qhead); return false; } } } justification * js = cls->get_justification(); if (js && !process_justification_for_minimization(js)) { reset_unmark_and_justifications(old_size, old_js_qhead); return false; } break; } case b_justification::BIN_CLAUSE: if (!process_antecedent_for_minimization(js.get_literal())) { reset_unmark_and_justifications(old_size, old_js_qhead); return false; } break; case b_justification::AXIOM: // it is a decision variable from a previous scope level or an assumption if (m_ctx.get_assign_level(var) > m_ctx.get_base_level()) { reset_unmark_and_justifications(old_size, old_js_qhead); return false; } break; case b_justification::JUSTIFICATION: if (m_ctx.is_assumption(var) || !process_justification_for_minimization(js.get_justification())) { reset_unmark_and_justifications(old_size, old_js_qhead); return false; } break; } } return true; } /** \brief Minimize the number of literals in learned_clause_lits. The main idea is to remove literals that are implied by other literals in m_lemma and/or literals assigned in the base levels. */ void conflict_resolution::minimize_lemma() { m_unmark.reset(); m_lvl_set = get_lemma_approx_level_set(); unsigned sz = m_lemma.size(); unsigned i = 1; // the first literal is the FUIP unsigned j = 1; for (; i < sz; i++) { literal l = m_lemma[i]; if (implied_by_marked(l)) { m_unmark.push_back(l.var()); } else { if (j != i) { m_lemma[j] = m_lemma[i]; m_lemma_atoms.set(j, m_lemma_atoms.get(i)); } j++; } } reset_unmark_and_justifications(0, 0); m_lemma .shrink(j); m_lemma_atoms.shrink(j); m_ctx.m_stats.m_num_minimized_lits += sz - j; } /** \brief Return the proof object associated with the equality (= n1 n2) if it already exists. Otherwise, return 0 and add p to the todo-list. */ proof * conflict_resolution::get_proof(enode * n1, enode * n2) { SASSERT(n1 != n2); proof * pr; if (m_eq2proof.find(n1, n2, pr)) { TRACE("proof_gen_bug", tout << "eq2_pr_cached: #" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n";); return pr; } m_todo_pr.push_back(tp_elem(n1, n2)); return nullptr; } /** \brief Apply symmetry if pr is a proof of (= n2 n1). */ proof * conflict_resolution::norm_eq_proof(enode * n1, enode * n2, proof * pr) { if (!pr) return nullptr; SASSERT(m_manager.has_fact(pr)); app * fact = to_app(m_manager.get_fact(pr)); app * n1_owner = n1->get_owner(); app * n2_owner = n2->get_owner(); bool is_eq = m_manager.is_eq(fact); if (!is_eq || (fact->get_arg(0) != n2_owner && fact->get_arg(1) != n2_owner)) { CTRACE("norm_eq_proof_bug", !m_ctx.is_true(n2) && !m_ctx.is_false(n2), tout << "n1: #" << n1->get_owner_id() << ", n2: #" << n2->get_owner_id() << "\n"; if (fact->get_num_args() == 2) { tout << "fact(0): #" << fact->get_arg(0)->get_id() << ", fact(1): #" << fact->get_arg(1)->get_id() << "\n"; } tout << mk_bounded_pp(n1->get_owner(), m_manager, 10) << "\n"; tout << mk_bounded_pp(n2->get_owner(), m_manager, 10) << "\n"; tout << mk_bounded_pp(fact, m_manager, 10) << "\n"; tout << mk_ll_pp(pr, m_manager, true, false);); SASSERT(m_ctx.is_true(n2) || m_ctx.is_false(n2)); SASSERT(fact == n1_owner || (m_manager.is_not(fact) && fact->get_arg(0) == n1_owner)); if (m_ctx.is_true(n2)) pr = m_manager.mk_iff_true(pr); else pr = m_manager.mk_iff_false(pr); m_new_proofs.push_back(pr); return pr; } TRACE("norm_eq_proof", tout << "#" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n"; tout << mk_ll_pp(pr, m_manager, true, false);); SASSERT(m_manager.is_eq(fact)); SASSERT((fact->get_arg(0) == n1->get_owner() && fact->get_arg(1) == n2->get_owner()) || (fact->get_arg(1) == n1->get_owner() && fact->get_arg(0) == n2->get_owner())); if (fact->get_arg(0) == n1_owner && fact->get_arg(1) == n2_owner) return pr; pr = m_manager.mk_symmetry(pr); m_new_proofs.push_back(pr); return pr; } /** \brief Return a proof object for n1 = n2 using the given eq_justification if its antecedents are already available. Otherwise, return 0 and add the missing antecedents to the todo-list. */ proof * conflict_resolution::get_proof(enode * n1, enode * n2, eq_justification js) { unsigned num_args; switch (js.get_kind()) { case eq_justification::AXIOM: UNREACHABLE(); return nullptr; case eq_justification::EQUATION: TRACE("proof_gen_bug", tout << js.get_literal() << "\n"; m_ctx.display_literal_info(tout, js.get_literal());); return norm_eq_proof(n1, n2, get_proof(js.get_literal())); case eq_justification::JUSTIFICATION: return norm_eq_proof(n1, n2, get_proof(js.get_justification())); case eq_justification::CONGRUENCE: num_args = n1->get_num_args(); SASSERT(num_args == n2->get_num_args()); SASSERT(n1->get_owner()->get_decl() == n2->get_owner()->get_decl()); if (js.used_commutativity()) { bool visited = true; SASSERT(num_args == 2); enode * c1_1 = n1->get_arg(0); enode * c1_2 = n1->get_arg(1); enode * c2_1 = n2->get_arg(0); enode * c2_2 = n2->get_arg(1); ptr_buffer prs; if (c1_1 != c2_2) { proof * pr = get_proof(c1_1, c2_2); prs.push_back(pr); if (!pr) visited = false; } if (c1_2 != c2_1) { proof * pr = get_proof(c1_2, c2_1); prs.push_back(pr); if (!pr) visited = false; } if (!visited) return nullptr; app * e1 = n1->get_owner(); app * e2 = n2->get_owner(); app * e2_prime = m_manager.mk_app(e2->get_decl(), e2->get_arg(1), e2->get_arg(0)); proof * pr1 = nullptr; if (!prs.empty()) { pr1 = m_manager.mk_congruence(e1, e2_prime, prs.size(), prs.c_ptr()); m_new_proofs.push_back(pr1); } else { TRACE("comm_proof_bug", tout << "e1: #" << e1->get_id() << " e2: #" << e2->get_id() << "\n" << mk_bounded_pp(e1, m_manager, 10) << "\n" << mk_bounded_pp(e2, m_manager, 10) << "\n";); // SASSERT(e1 == e2); } proof * pr2 = m_manager.mk_commutativity(e2_prime); m_new_proofs.push_back(pr2); return m_manager.mk_transitivity(pr1, pr2); } else { bool visited = true; ptr_buffer prs; for (unsigned i = 0; i < num_args; i++) { enode * c1 = n1->get_arg(i); enode * c2 = n2->get_arg(i); if (c1 != c2) { proof * pr = get_proof(c1, c2); prs.push_back(pr); if (!pr) visited = false; } } if (!visited) return nullptr; proof * pr = m_manager.mk_congruence(n1->get_owner(), n2->get_owner(), prs.size(), prs.c_ptr()); m_new_proofs.push_back(pr); return pr; } default: UNREACHABLE(); return nullptr; } } /** \brief Return the proof object associated with the given literal if it already exists. Otherwise, return 0 and add l to the todo-list. */ proof * conflict_resolution::get_proof(literal l) { proof * pr; if (m_lit2proof.find(l, pr)) { TRACE("proof_gen_bug", tout << "lit2pr_cached: #" << l << "\n";); return pr; } m_todo_pr.push_back(tp_elem(l)); return nullptr; } /** \brief Return a proof object for l using the given b_justification if its antecedents are already available. Otherwise, return 0 and add the missing antecedents to the todo-list. */ proof * conflict_resolution::get_proof(literal l, b_justification js) { #ifdef _TRACE static unsigned invocation_counter = 0; invocation_counter++; #define DUMP_AFTER_NUM_INVOCATIONS 213473 CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, tout << "START get_proof\n";); #endif // l is a hypothesis: if it is marked, and the justification for the variable l.var() is js. // we need the second condition, because the core builds proofs as: // // p1: is a proof of "A" // p2: is a proof of "not A" // [unit-resolution p1 p2]: false // // Let us assume that "A" was assigned first during propagation. // Then, the "resolve" method will never select "not A" as a hypothesis. // "not_A" will be the not_l argument in this method. // Since we are assuming that "A" was assigned first", m_ctx.get_justification("A") will be // p1. // // So, the test "m_ctx.get_justification(l.var()) == js" is used to check // if l was assigned before ~l. if ((m_ctx.is_marked(l.var()) && m_ctx.get_justification(l.var()) == js) || (js.get_kind() == b_justification::AXIOM)) { expr_ref l_expr(m_manager); m_ctx.literal2expr(l, l_expr); proof * pr = m_manager.mk_hypothesis(l_expr.get()); m_new_proofs.push_back(pr); return pr; } else { SASSERT(js.get_kind() != b_justification::BIN_CLAUSE); SASSERT(js.get_kind() != b_justification::AXIOM); if (js.get_kind() == b_justification::CLAUSE) { clause * cls = js.get_clause(); justification * js = cls->get_justification(); SASSERT(js); proof * pr = get_proof(js); ptr_buffer prs; bool visited = pr != nullptr; TRACE("get_proof_bug", if (pr != 0) tout << js->get_name() << "\n";); CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, if (pr != 0) tout << js->get_name() << "\n";); CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, if (pr != 0) js->display_debug_info(*this, tout);); prs.push_back(pr); unsigned num_lits = cls->get_num_literals(); unsigned i = 0; SASSERT(l == false_literal || l == cls->get_literal(0) || l == cls->get_literal(1)); if (l != false_literal) { if (cls->get_literal(0) == l) { i = 1; } else { SASSERT(l == cls->get_literal(1)); proof * pr = get_proof(~cls->get_literal(0)); prs.push_back(pr); if (!pr) visited = false; i = 2; } } for (; i < num_lits; i++) { proof * pr = get_proof(~cls->get_literal(i)); prs.push_back(pr); if (!pr) visited = false; } if (!visited) return nullptr; expr_ref l_exr(m_manager); m_ctx.literal2expr(l, l_exr); TRACE("get_proof_bug", tout << "clause:\n"; for (unsigned i = 0; i < num_lits; i++) { tout << cls->get_literal(i).index() << "\n"; expr_ref l_expr(m_manager); m_ctx.literal2expr(cls->get_literal(i), l_expr); tout << mk_pp(l_expr, m_manager) << "\n"; } tout << "antecedents:\n"; for (unsigned i = 0; i < prs.size(); i++) { tout << mk_pp(m_manager.get_fact(prs[i]), m_manager) << "\n"; } tout << "consequent:\n" << mk_pp(l_exr, m_manager) << "\n";); CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, tout << "clause, num_lits: " << num_lits << ":\n"; for (unsigned i = 0; i < num_lits; i++) { tout << cls->get_literal(i).index() << "\n"; expr_ref l_expr(m_manager); m_ctx.literal2expr(cls->get_literal(i), l_expr); tout << mk_pp(l_expr, m_manager) << "\n"; } tout << "antecedents:\n"; for (unsigned i = 0; i < prs.size(); i++) { tout << mk_pp(m_manager.get_fact(prs[i]), m_manager) << "\n"; } tout << "consequent:\n" << mk_pp(l_exr, m_manager) << "\n";); TRACE("get_proof", tout << l.index() << " " << true_literal.index() << " " << false_literal.index() << " "; m_ctx.display_literal(tout, l); tout << " --->\n"; tout << mk_ll_pp(l_exr, m_manager);); CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, tout << l.index() << " " << true_literal.index() << " " << false_literal.index() << " "; m_ctx.display_literal(tout, l); tout << " --->\n"; tout << mk_ll_pp(l_exr, m_manager);); pr = m_manager.mk_unit_resolution(prs.size(), prs.c_ptr(), l_exr); m_new_proofs.push_back(pr); return pr; } else { return get_proof(js.get_justification()); } } } /** \brief Return the proof object associated with the given justification if it already exists. Otherwise, return 0 and add js to the todo-list. */ proof * conflict_resolution::get_proof(justification * js) { proof * pr; if (m_js2proof.find(js, pr)) { TRACE("proof_gen_bug", tout << "js2pr_cached: #" << js << "\n";); return pr; } SASSERT(js != 0); TRACE("proof_gen_bug", tout << js << "\n";); m_todo_pr.push_back(tp_elem(js)); return nullptr; } void conflict_resolution::init_mk_proof() { TRACE("proof_gen_bug", tout << "reset_caches\n";); m_new_proofs.reset(); m_todo_pr.reset(); m_eq2proof.reset(); m_lit2proof.reset(); m_js2proof.reset(); for (literal lit : m_lemma) m_ctx.set_mark(lit.var()); } bool conflict_resolution::visit_b_justification(literal l, b_justification js) { // l is a hypothesis: if it is marked, and the justification for the variable l.var() is js. // See: get_proof(literal l, b_justification js) if (m_ctx.is_marked(l.var()) && m_ctx.get_justification(l.var()) == js) return true; SASSERT(js.get_kind() != b_justification::BIN_CLAUSE); CTRACE("visit_b_justification_bug", js.get_kind() == b_justification::AXIOM, tout << "l: " << l << "\n"; m_ctx.display(tout);); if (js.get_kind() == b_justification::AXIOM) return true; SASSERT(js.get_kind() != b_justification::AXIOM); if (js.get_kind() == b_justification::CLAUSE) { clause * cls = js.get_clause(); bool visited = get_proof(cls->get_justification()) != nullptr; unsigned num_lits = cls->get_num_literals(); unsigned i = 0; if (l != false_literal) { if (cls->get_literal(0) == l) { i = 1; } else { SASSERT(cls->get_literal(1) == l); if (get_proof(~cls->get_literal(0)) == nullptr) visited = false; i = 2; } } for (; i < num_lits; i++) { SASSERT(cls->get_literal(i) != l); if (get_proof(~cls->get_literal(i)) == nullptr) visited = false; } return visited; } else return get_proof(js.get_justification()) != nullptr; } void conflict_resolution::mk_proof(literal l, b_justification js) { SASSERT(!m_lit2proof.contains(l)); proof * pr = get_proof(l, js); SASSERT(pr); TRACE("proof_gen_bug", tout << "lit2pr_saved: #" << l << "\n";); m_lit2proof.insert(l, pr); TRACE("mk_proof", tout << mk_bounded_pp(m_ctx.bool_var2expr(l.var()), m_manager, 10) << "\n"; tout << "storing proof for: "; m_ctx.display_literal(tout, l); tout << "\n"; tout << mk_ll_pp(pr, m_manager);); } /** \brief Given that lhs = ... = rhs, and lhs reaches rhs in the 'proof' tree branch. Then, return true if all proof objects needed to create the proof steps are already available. Otherwise return false and update m_todo_pr with info about the proof objects that need to be created. */ bool conflict_resolution::visit_trans_proof(enode * lhs, enode * rhs) { SASSERT(lhs->trans_reaches(rhs)); bool visited = true; while (lhs != rhs) { eq_justification js = lhs->m_trans.m_justification; switch (js.get_kind()) { case eq_justification::AXIOM: UNREACHABLE(); break; case eq_justification::EQUATION: if (get_proof(js.get_literal()) == nullptr) visited = false; break; case eq_justification::JUSTIFICATION: if (get_proof(js.get_justification()) == nullptr) visited = false; break; case eq_justification::CONGRUENCE: { enode * n1 = lhs; enode * n2 = lhs->m_trans.m_target; unsigned num_args = n1->get_num_args(); SASSERT(num_args == n2->get_num_args()); if (js.used_commutativity()) { SASSERT(num_args == 2); enode * c1_1 = n1->get_arg(0); enode * c1_2 = n1->get_arg(1); enode * c2_1 = n2->get_arg(0); enode * c2_2 = n2->get_arg(1); if (c1_1 != c2_2 && get_proof(c1_1, c2_2) == nullptr) visited = false; if (c1_2 != c2_1 && get_proof(c1_2, c2_1) == nullptr) visited = false; } else { for (unsigned i = 0; i < num_args; i++) { enode * c1 = n1->get_arg(i); enode * c2 = n2->get_arg(i); if (c1 != c2 && get_proof(c1, c2) == nullptr) visited = false; } } break; } default: UNREACHABLE(); } lhs = lhs->m_trans.m_target; } return visited; } /** \brief Return true if all proof objects that are used to build the proof that lhs = rhs were already built. If the result is false, then m_todo_pr is updated with info about the proof objects that need to be created. */ bool conflict_resolution::visit_eq_justications(enode * lhs, enode * rhs) { enode * c = find_common_ancestor(lhs, rhs); bool v1 = visit_trans_proof(lhs, c); bool v2 = visit_trans_proof(rhs, c); return v1 && v2; } /** \brief Given that lhs = ... = rhs, and lhs reaches rhs in the trans proof branch, then build a proof object for each equality in the sequence, and insert them into result. */ void conflict_resolution::mk_proof(enode * lhs, enode * rhs, ptr_buffer & result) { SASSERT(lhs->trans_reaches(rhs)); while (lhs != rhs) { eq_justification js = lhs->m_trans.m_justification; enode * n1 = lhs; enode * n2 = lhs->m_trans.m_target; proof * pr = get_proof(n1, n2, js); SASSERT(pr); result.push_back(pr); lhs = lhs->m_trans.m_target; } } void conflict_resolution::mk_proof(enode * lhs, enode * rhs) { SASSERT(!m_eq2proof.contains(lhs, rhs)); enode * c = find_common_ancestor(lhs, rhs); ptr_buffer prs1; mk_proof(lhs, c, prs1); ptr_buffer prs2; mk_proof(rhs, c, prs2); while (!prs2.empty()) { proof * pr = prs2.back(); if (m_manager.proofs_enabled()) { pr = m_manager.mk_symmetry(pr); m_new_proofs.push_back(pr); prs1.push_back(pr); } else { prs1.push_back(pr); } prs2.pop_back(); } proof * pr = nullptr; SASSERT(!prs1.empty()); if (prs1.size() == 1) pr = prs1[0]; else { TRACE("mk_transitivity", unsigned sz = prs1.size(); for (unsigned i = 0; i < sz; i++) { tout << mk_ll_pp(prs1[i], m_manager) << "\n"; }); pr = m_manager.mk_transitivity(prs1.size(), prs1.c_ptr(), lhs->get_owner(), rhs->get_owner()); } m_new_proofs.push_back(pr); TRACE("proof_gen_bug", tout << "eq2pr_saved: #" << lhs->get_owner_id() << " #" << rhs->get_owner_id() << "\n";); m_eq2proof.insert(lhs, rhs, pr); } void conflict_resolution::mk_conflict_proof(b_justification conflict, literal not_l) { SASSERT(conflict.get_kind() != b_justification::BIN_CLAUSE); SASSERT(conflict.get_kind() != b_justification::AXIOM); SASSERT(not_l == null_literal || conflict.get_kind() == b_justification::JUSTIFICATION); TRACE("mk_conflict_proof", tout << "lemma literals:"; for (literal lit : m_lemma) { m_ctx.display_literal(tout << " ", lit); } tout << "\n";); init_mk_proof(); literal consequent; if (not_l == null_literal) { consequent = false_literal; } else { consequent = ~not_l; m_todo_pr.push_back(tp_elem(not_l)); } visit_b_justification(consequent, conflict); while (!m_todo_pr.empty()) { tp_elem & elem = m_todo_pr.back(); switch (elem.m_kind) { case tp_elem::EQUALITY: { enode * lhs = elem.m_lhs; enode * rhs = elem.m_rhs; if (m_eq2proof.contains(lhs, rhs)) m_todo_pr.pop_back(); else if (visit_eq_justications(lhs, rhs)) { m_todo_pr.pop_back(); mk_proof(lhs, rhs); } break; } case tp_elem::JUSTIFICATION: { justification * js = elem.m_js; if (m_js2proof.contains(js)) m_todo_pr.pop_back(); else { proof * pr = js->mk_proof(*this); if (pr) { m_todo_pr.pop_back(); m_new_proofs.push_back(pr); TRACE("proof_gen_bug", tout << "js2pr_saved: #" << js << "\n";); m_js2proof.insert(js, pr); } } break; } case tp_elem::LITERAL: { literal l = to_literal(elem.m_lidx); if (m_lit2proof.contains(l)) m_todo_pr.pop_back(); else { b_justification js = m_ctx.get_justification(l.var()); if (visit_b_justification(l, js)) { m_todo_pr.pop_back(); mk_proof(l, js); } } break; } default: UNREACHABLE(); } } SASSERT(visit_b_justification(consequent, conflict)); proof * pr = nullptr; if (not_l == null_literal) { pr = get_proof(false_literal, conflict); SASSERT(pr); } else { proof * prs[2] = { nullptr, nullptr}; m_lit2proof.find(not_l, prs[0]); SASSERT(prs[0]); prs[1] = get_proof(consequent, conflict); SASSERT(prs[1]); pr = m_manager.mk_unit_resolution(2, prs); } expr_ref_buffer lits(m_manager); for (literal lit : m_lemma) { m_ctx.unset_mark(lit.var()); expr_ref l_expr(m_manager); m_ctx.literal2expr(lit, l_expr); lits.push_back(l_expr); } expr * fact = nullptr; switch (lits.size()) { case 0: fact = nullptr; break; case 1: fact = lits[0]; break; default: fact = m_manager.mk_or(lits.size(), lits.c_ptr()); } if (fact == nullptr) m_lemma_proof = pr; else m_lemma_proof = m_manager.mk_lemma(pr, fact); m_new_proofs.reset(); } void conflict_resolution::process_antecedent_for_unsat_core(literal antecedent) { bool_var var = antecedent.var(); TRACE("conflict", tout << "processing antecedent: "; m_ctx.display_literal_info(tout << antecedent << " ", antecedent); tout << (m_ctx.is_marked(var)?"marked":"not marked"); tout << "\n";); if (!m_ctx.is_marked(var)) { m_ctx.set_mark(var); m_unmark.push_back(var); } if (m_ctx.is_assumption(var)) { m_assumptions.push_back(antecedent); } } void conflict_resolution::process_justification_for_unsat_core(justification * js) { literal_vector & antecedents = m_tmp_literal_vector; antecedents.reset(); justification2literals_core(js, antecedents); for (literal lit : antecedents) process_antecedent_for_unsat_core(lit); } void conflict_resolution::mk_unsat_core(b_justification conflict, literal not_l) { SASSERT(m_ctx.tracking_assumptions()); m_assumptions.reset(); m_unmark.reset(); SASSERT(m_conflict_lvl <= m_ctx.get_search_level()); unsigned search_lvl = m_ctx.get_search_level(); b_justification js = conflict; literal consequent = false_literal; if (not_l != null_literal) { consequent = ~not_l; } int idx = skip_literals_above_conflict_level(); if (not_l != null_literal) process_antecedent_for_unsat_core(consequent); if (m_assigned_literals.empty()) { goto end_unsat_core; } while (true) { TRACE("unsat_core_bug", tout << consequent << ", idx: " << idx << " " << js.get_kind() << "\n";); switch (js.get_kind()) { case b_justification::CLAUSE: { clause * cls = js.get_clause(); TRACE("unsat_core_bug", m_ctx.display_clause_detail(tout, cls);); unsigned num_lits = cls->get_num_literals(); unsigned i = 0; if (consequent != false_literal) { SASSERT(cls->get_literal(0) == consequent || cls->get_literal(1) == consequent); if (cls->get_literal(0) == consequent) { i = 1; } else { process_antecedent_for_unsat_core(~cls->get_literal(0)); i = 2; } } for(; i < num_lits; i++) { literal l = cls->get_literal(i); process_antecedent_for_unsat_core(~l); } justification * js = cls->get_justification(); if (js) { process_justification_for_unsat_core(js); } break; } case b_justification::BIN_CLAUSE: SASSERT(consequent.var() != js.get_literal().var()); process_antecedent_for_unsat_core(js.get_literal()); break; case b_justification::AXIOM: break; case b_justification::JUSTIFICATION: process_justification_for_unsat_core(js.get_justification()); break; default: UNREACHABLE(); } if (m_ctx.is_assumption(consequent.var())) { m_assumptions.push_back(consequent); } while (idx >= 0) { literal l = m_assigned_literals[idx]; CTRACE("unsat_core_bug", m_ctx.is_marked(l.var()), tout << "l: " << l << ", get_assign_level(l): " << m_ctx.get_assign_level(l) << "\n";); if (m_ctx.get_assign_level(l) < search_lvl) goto end_unsat_core; if (m_ctx.is_marked(l.var())) break; idx--; } if (idx < 0) { goto end_unsat_core; } SASSERT(idx >= 0); consequent = m_assigned_literals[idx]; bool_var c_var = consequent.var(); SASSERT(m_ctx.get_assign_level(c_var) == search_lvl); js = m_ctx.get_justification(c_var); idx--; } end_unsat_core: TRACE("unsat_core", tout << "assumptions:\n"; m_ctx.display_literals(tout, m_assumptions); tout << "\n";); reset_unmark_and_justifications(0, 0); } conflict_resolution * mk_conflict_resolution(ast_manager & m, context & ctx, dyn_ack_manager & dack_manager, smt_params const & params, literal_vector const & assigned_literals, vector & watches) { return alloc(conflict_resolution, m, ctx, dack_manager, params, assigned_literals, watches); } };