3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-10 19:27:06 +00:00
z3/lib/smt_context.cpp
Leonardo de Moura e9eab22e5c Z3 sources
Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
2012-10-02 11:35:25 -07:00

4208 lines
170 KiB
C++

/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
smt_context.cpp
Abstract:
<abstract>
Author:
Leonardo de Moura (leonardo) 2008-02-19.
Revision History:
--*/
#include<math.h>
#include"smt_context.h"
#include"luby.h"
#include"ast_pp.h"
#include"ast_ll_pp.h"
#include"warning.h"
#include"smt_quick_checker.h"
#include"proof_checker.h"
#include"ast_util.h"
#include"uses_theory.h"
#include"model.h"
#include"smt_for_each_relevant_expr.h"
#include"timeit.h"
#include"well_sorted.h"
#include"union_find.h"
#include"smt_model_generator.h"
#include"smt_model_checker.h"
#include"smt_model_finder.h"
#include"model_pp.h"
#include"ast_smt2_pp.h"
namespace smt {
context::context(ast_manager & m, front_end_params & p, params_ref const & _p):
m_manager(m),
m_fparams(p),
m_params(_p),
m_setup(*this, p),
m_cancel_flag(false),
m_asserted_formulas(m, p),
m_qmanager(alloc(quantifier_manager, *this, p, _p)),
m_model_generator(alloc(model_generator, m)),
m_relevancy_propagator(mk_relevancy_propagator(*this)),
m_random(p.m_random_seed),
m_flushing(false),
m_progress_callback(0),
m_next_progress_sample(0),
m_fingerprints(m_region),
m_b_internalized_stack(m),
m_e_internalized_stack(m),
m_final_check_idx(0),
m_cg_table(m),
m_dyn_ack_manager(*this, p),
m_is_diseq_tmp(0),
m_units_to_reassert(m_manager),
m_qhead(0),
m_simp_qhead(0),
m_simp_counter(0),
m_bvar_inc(1.0),
m_phase_cache_on(true),
m_phase_counter(0),
m_phase_default(false),
m_conflict(null_b_justification),
m_not_l(null_literal),
m_conflict_resolution(mk_conflict_resolution(m, *this, m_dyn_ack_manager, p, m_assigned_literals, m_watches)),
m_unsat_proof(m),
m_unsat_core(m),
#ifdef Z3DEBUG
m_trail_enabled(true),
#endif
m_scope_lvl(0),
m_base_lvl(0),
m_search_lvl(0),
m_generation(0),
m_last_search_result(l_undef),
m_last_search_failure(UNKNOWN),
m_searching(false) {
SASSERT(m_scope_lvl == 0);
SASSERT(m_base_lvl == 0);
SASSERT(m_search_lvl == 0);
m_case_split_queue = mk_case_split_queue(*this, p);
init();
if (!relevancy())
m_fparams.m_relevancy_lemma = false;
m_model_generator->set_context(this);
}
context::~context() {
flush();
}
context * context::mk_fresh(symbol const * l, front_end_params * p) {
context * new_ctx = alloc(context, m_manager, p == 0 ? m_fparams : *p);
new_ctx->set_logic(l == 0 ? m_setup.get_logic() : *l);
// copy missing simplifier_plugins
// remark: some simplifier_plugins are automatically created by the asserted_formulas class.
simplifier & s = get_simplifier();
simplifier & new_s = new_ctx->get_simplifier();
ptr_vector<simplifier_plugin>::const_iterator it1 = s.begin_plugins();
ptr_vector<simplifier_plugin>::const_iterator end1 = s.end_plugins();
for (; it1 != end1; ++it1) {
simplifier_plugin * p = *it1;
if (new_s.get_plugin(p->get_family_id()) == 0) {
new_ctx->register_plugin(p->mk_fresh());
}
SASSERT(new_s.get_plugin(p->get_family_id()) != 0);
}
// copy theory plugins
ptr_vector<theory>::iterator it2 = m_theory_set.begin();
ptr_vector<theory>::iterator end2 = m_theory_set.end();
for (; it2 != end2; ++it2) {
theory * new_th = (*it2)->mk_fresh(new_ctx);
new_ctx->register_plugin(new_th);
}
new_ctx->m_setup.mark_already_configured();
return new_ctx;
}
void context::init() {
app * t = m_manager.mk_true();
mk_bool_var(t);
SASSERT(get_bool_var(t) == true_bool_var);
SASSERT(true_literal.var() == true_bool_var);
m_assignment[true_literal.index()] = l_true;
m_assignment[false_literal.index()] = l_false;
if (m_manager.proofs_enabled()) {
proof * pr = m_manager.mk_true_proof();
m_bdata[true_bool_var].m_justification = b_justification(mk_justification(justification_proof_wrapper(*this, pr)));
}
else {
m_bdata[true_bool_var].m_justification = b_justification::mk_axiom();
}
m_true_enode = mk_enode(t, true, true, false);
// internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant.
// m_true_enode->mark_as_interpreted();
app * f = m_manager.mk_false();
m_false_enode = mk_enode(f, true, true, false);
// m_false_enode->mark_as_interpreted();
}
void context::set_progress_callback(progress_callback *cb)
{
m_progress_callback = cb;
}
/**
\brief This method should be used to create equality atoms during the search.
See comments in theory::mk_eq_atom
*/
app * context::mk_eq_atom(expr * lhs, expr * rhs) {
family_id fid = m_manager.get_sort(lhs)->get_family_id();
theory * th = get_theory(fid);
if (th)
return th->mk_eq_atom(lhs, rhs);
if (lhs->get_id() > rhs->get_id())
std::swap(lhs, rhs);
return m_manager.mk_eq(lhs, rhs);
}
void context::assign_core(literal l, b_justification j, bool decision) {
TRACE("assign_core", tout << "assigning: " << l << " "; display_literal(tout, l); tout << "\n";);
SASSERT(l.var() < static_cast<int>(m_b_internalized_stack.size()));
m_assigned_literals.push_back(l);
m_assignment[l.index()] = l_true;
m_assignment[(~l).index()] = l_false;
bool_var_data & d = get_bdata(l.var());
d.m_justification = j;
d.m_scope_lvl = m_scope_lvl;
if (m_fparams.m_restart_adaptive && d.m_phase_available) {
m_agility *= m_fparams.m_agility_factor;
if (!decision && d.m_phase == l.sign())
m_agility += (1.0 - m_fparams.m_agility_factor);
}
d.m_phase_available = true;
d.m_phase = !l.sign();
TRACE("phase_selection", tout << "saving phase, is_pos: " << d.m_phase << " l: " << l << "\n";);
if (d.is_atom() && (m_fparams.m_relevancy_lvl == 0 || (m_fparams.m_relevancy_lvl == 1 && !d.is_quantifier()) || is_relevant_core(bool_var2expr(l.var()))))
m_atom_propagation_queue.push_back(l);
#ifndef SMTCOMP
if (m_fparams.m_trace_stream != NULL)
trace_assign(l, j, decision);
m_case_split_queue->assign_lit_eh(l);
#endif
}
bool context::bcp() {
SASSERT(!inconsistent());
while (m_qhead < m_assigned_literals.size()) {
literal l = m_assigned_literals[m_qhead];
SASSERT(get_assignment(l) == l_true);
m_qhead++;
m_simp_counter--;
literal not_l = ~l;
SASSERT(get_assignment(not_l) == l_false);
watch_list & w = m_watches[l.index()];
if (binary_clause_opt_enabled()) {
// binary clause propagation
b_justification js(l);
literal * it = w.begin_literals();
literal * end = w.end_literals();
for(; it != end; ++it) {
literal l = *it;
switch (get_assignment(l)) {
case l_false:
m_stats.m_num_bin_propagations++;
set_conflict(js, ~l);
return false;
case l_undef:
m_stats.m_num_bin_propagations++;
assign_core(l, js);
break;
case l_true:
break;
}
if (m_cancel_flag) {
return true;
}
}
}
// non-binary clause propagation
watch_list::clause_iterator it = w.begin_clause();
watch_list::clause_iterator it2 = it;
watch_list::clause_iterator end = w.end_clause();
for(; it != end; ++it) {
clause * cls = *it;
CTRACE("bcp_bug", cls->get_literal(0) != not_l && cls->get_literal(1) != not_l, display_clause_detail(tout, cls);
tout << "not_l: "; display_literal(tout, not_l); tout << " " << not_l << "\n";);
SASSERT(cls->get_literal(0) == not_l || cls->get_literal(1) == not_l);
if (cls->get_literal(0) == not_l) {
cls->set_literal(0, cls->get_literal(1));
cls->set_literal(1, not_l);
}
SASSERT(cls->get_literal(1) == not_l);
literal first_lit = cls->get_literal(0);
lbool first_lit_val = get_assignment(first_lit);
if (first_lit_val == l_true) {
*it2 = *it; // clause is already satisfied, keep it
it2++;
}
else {
literal * it3 = cls->begin_literals() + 2;
literal * end3 = cls->end_literals();
for(; it3 != end3; ++it3) {
if (get_assignment(*it3) != l_false) {
// swap literal *it3 with literal at position 0
// the negation of literal *it3 will watch clause cls.
m_watches[(~(*it3)).index()].insert_clause(cls);
cls->set_literal(1, *it3);
*it3 = not_l;
goto found_watch;
}
}
// did not find watch...
if (first_lit_val == l_false) {
// CONFLICT
// copy remaining watches
while (it < end) {
*it2 = *it;
it2++;
it++;
}
SASSERT(it2 <= end);
w.set_end_clause(it2);
SASSERT(is_empty_clause(cls));
set_conflict(b_justification(cls));
return false;
}
else {
// PROPAGATION
SASSERT(first_lit_val == l_undef);
SASSERT(get_assignment(first_lit) == l_undef);
SASSERT(is_unit_clause(cls));
*it2 = *it;
it2++; // keep clause
m_stats.m_num_propagations++;
// It is safe to call assign_core instead of assign because first_lit is unassigned
assign_core(first_lit, b_justification(cls));
if (m_fparams.m_relevancy_lemma && cls->is_lemma()) {
expr * e = bool_var2expr(first_lit.var());
// IMPORTANT: this kind of propagation asserts negative literals of the form (= A1 A2) where
// A1 and A2 are array terms. So, it may be interesting to disable it for array eqs.
//if (!(m_manager.is_eq(e) && m_manager.get_sort(to_app(e)->get_arg(0))->get_family_id() == m_manager.get_family_id("array")))
mark_as_relevant(e);
}
}
found_watch:;
}
}
SASSERT(it2 <= end);
w.set_end_clause(it2);
}
return true;
}
/**
\brief Push a new equality for theory th, into the theory equality propagation queue.
*/
void context::push_new_th_eq(theory_id th, theory_var lhs, theory_var rhs) {
SASSERT(lhs != rhs);
SASSERT(lhs != null_theory_var);
SASSERT(rhs != null_theory_var);
SASSERT(th != null_theory_id);
SASSERT(get_theory(th)->get_enode(lhs) != get_theory(th)->get_enode(rhs));
m_th_eq_propagation_queue.push_back(new_th_eq(th, lhs, rhs));
}
/**
\brief Push a new disequality for theory th, into the theory disequality propagation queue.
*/
void context::push_new_th_diseq(theory_id th, theory_var lhs, theory_var rhs) {
SASSERT(lhs != rhs);
SASSERT(lhs != null_theory_var);
SASSERT(rhs != null_theory_var);
SASSERT(th != null_theory_id);
theory * t = get_theory(th);
if (t->get_enode(lhs)->is_interpreted() && t->get_enode(rhs)->is_interpreted())
return;
TRACE("add_diseq",
tout << "#" << t->get_enode(lhs)->get_owner_id() << " != "
<< "#" << t->get_enode(rhs)->get_owner_id() << "\n";);
m_th_diseq_propagation_queue.push_back(new_th_eq(th, lhs, rhs));
}
class add_eq_trail : public trail<context> {
enode * m_r1;
enode * m_n1;
unsigned m_r2_num_parents;
public:
add_eq_trail(enode * r1, enode * n1, unsigned r2_num_parents):
m_r1(r1),
m_n1(n1),
m_r2_num_parents(r2_num_parents) {
}
virtual void undo(context & ctx) {
ctx.undo_add_eq(m_r1, m_n1, m_r2_num_parents);
}
};
/**
\brief Add the equality n1 = n2 with justification js into the logical context.
*/
void context::add_eq(enode * n1, enode * n2, eq_justification js) {
unsigned old_trail_size = m_trail_stack.size();
try {
TRACE("add_eq", tout << "assigning: #" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n";);
TRACE("add_eq_detail", tout << "assigning\n" << mk_pp(n1->get_owner(), m_manager) << "\n" << mk_pp(n2->get_owner(), m_manager) << "\n";
tout << "kind: " << js.get_kind() << "\n";);
m_stats.m_num_add_eq++;
enode * r1 = n1->get_root();
enode * r2 = n2->get_root();
if (r1 == r2) {
TRACE("add_eq", tout << "redundant constraint.\n";);
return;
}
if (r1->is_interpreted() && r2->is_interpreted()) {
TRACE("add_eq", tout << "interpreted roots conflict.\n";);
set_conflict(mk_justification(eq_conflict_justification(n1, n2, js)));
return;
}
// Swap r1 and r2:
// 1. if the "equivalence" class of r1 is bigger than the equivalence class of r2
// OR
// 2. r1 is interpreted but r2 is not.
//
// The second condition is used to enforce the invariant that if a class contain
// an interepreted enode then the root is also interpreted.
if ((r1->get_class_size() > r2->get_class_size() && !r2->is_interpreted()) || r1->is_interpreted()) {
SASSERT(!r2->is_interpreted());
std::swap(n1, n2);
std::swap(r1, r2);
}
TRACE("add_eq", tout << "merging: #" << r1->get_owner_id() << " #" << r2->get_owner_id() <<
" n1: #" << n1->get_owner_id() << "\n";);
// It is necessary to propagate relevancy to other elements of
// the equivalence class. This is nessary to enforce the invariant
// in the field m_parent of the enode class.
if (is_relevant(r1)) { // && !m_manager.is_eq(r1->get_owner())) !is_eq HACK
// NOTE for !is_eq HACK... the !is_eq HACK does not propagate relevancy when two
// equality enodes are congruent. I tested this optimization because in V1
// relevancy is not propagated for congruent equalities.
// This occurs in V2, because we use the congruence table to propagate disequalities
// efficiently.
// I disabled this optimization HACK because it breaks invariants in the rest of the code.
// To use it, I need to go over the code and analyze all affected places.
mark_as_relevant(r2);
}
else if (is_relevant(r2)) { // && !m_manager.is_eq(r2->get_owner())) { // !is_eq HACK
mark_as_relevant(r1);
}
push_trail(add_eq_trail(r1, n1, r2->get_num_parents()));
m_qmanager->add_eq_eh(r1, r2);
merge_theory_vars(n2, n1, js);
// 'Proof' tree
// n1 -> ... -> r1
// n2 -> ... -> r2
SASSERT(n1->trans_reaches(r1));
invert_trans(n1);
n1->m_trans.m_target = n2;
n1->m_trans.m_justification = js;
SASSERT(r1->trans_reaches(n1));
// ---------------
// r1 -> .. -> n1 -> n2 -> ... -> r2
#if 0
{
static unsigned counter = 0;
static unsigned num_adds = 0;
static unsigned num_bad_adds = 0;
num_adds++;
if (r1->get_class_size() <= r2->get_class_size() &&
r1->m_parents.size() > r2->m_parents.size()) {
num_bad_adds++;
}
if (num_adds % 100000 == 0) {
verbose_stream() << "[add-eq]: " << num_bad_adds << " " << num_adds << " "
<< static_cast<double>(num_bad_adds)/static_cast<double>(num_adds) << "\n";
}
}
#endif
remove_parents_from_cg_table(r1);
enode * curr = r1;
do {
curr->m_root = r2;
curr = curr->m_next;
}
while(curr != r1);
SASSERT(r1->get_root() == r2);
reinsert_parents_into_cg_table(r1, r2, n1, n2, js);
if (n2->is_bool())
propagate_bool_enode_assignment(r1, r2, n1, n2);
// Merge "equivalence" classes
std::swap(r1->m_next, r2->m_next);
// Update "equivalence" class size
r2->m_class_size += r1->m_class_size;
CASSERT("add_eq", check_invariant());
}
catch (...) {
// Restore trail size since procedure was interrupted in the middle.
// If the add_eq_trail remains on the trail stack, then Z3 may crash when the destructor is invoked.
m_trail_stack.shrink(old_trail_size);
throw;
}
}
/**
\brief When merging to equivalence classes, the parents of the smallest one (that are congruence roots),
must be removed from the congruence table since their hash code will change.
*/
void context::remove_parents_from_cg_table(enode * r1) {
// Remove parents from the congruence table
enode_vector::iterator it = r1->begin_parents();
enode_vector::iterator end = r1->end_parents();
for (; it != end; ++it) {
enode * parent = *it;
#if 0
{
static unsigned num_eqs = 0;
static unsigned num_parents = 0;
static unsigned counter = 0;
if (parent->is_eq())
num_eqs++;
num_parents++;
if (num_parents % 100000 == 0) {
verbose_stream() << "[remove-cg] " << num_eqs << " " << num_parents << " "
<< static_cast<double>(num_eqs)/static_cast<double>(num_parents) << "\n";
}
}
#endif
SASSERT(parent->is_marked() || !parent->is_cgc_enabled() ||
(!parent->is_true_eq() && parent->is_cgr() == m_cg_table.contains_ptr(parent)) ||
(parent->is_true_eq() && !m_cg_table.contains_ptr(parent)));
if (!parent->is_marked() && parent->is_cgr() && !parent->is_true_eq()) {
TRACE("add_eq_parents", tout << "add_eq removing: #" << parent->get_owner_id() << "\n";);
SASSERT(!parent->is_cgc_enabled() || m_cg_table.contains_ptr(parent));
parent->set_mark();
if (parent->is_cgc_enabled()) {
m_cg_table.erase(parent);
SASSERT(!m_cg_table.contains_ptr(parent));
}
}
}
}
/**
\brief Reinsert the parents of r1 that were removed from the
cg_table at remove_parents_from_cg_table. Some of these parents will
become congruent to other enodes, and a new equality will be propagated.
Moreover, this method is also used for doing equality propagation.
The parents of r1 that remain as congruence roots are copied to the
r2->m_parents.
The n1, n2, js arguments are used to implement dynamic ackermanization.
js is a justification for n1 and n2 being equal, and the equality n1 = n2 is
the one that implied r1 = r2.
*/
void context::reinsert_parents_into_cg_table(enode * r1, enode * r2, enode * n1, enode * n2, eq_justification js) {
enode_vector & r2_parents = r2->m_parents;
enode_vector::iterator it = r1->begin_parents();
enode_vector::iterator end = r1->end_parents();
for (; it != end; ++it) {
enode * parent = *it;
if (!parent->is_marked())
continue;
parent->unset_mark();
if (parent->is_eq()) {
SASSERT(parent->get_num_args() == 2);
TRACE("add_eq_bug", tout << "visiting: #" << parent->get_owner_id() << "\n";);
enode * lhs = parent->get_arg(0);
enode * rhs = parent->get_arg(1);
if (lhs->get_root() == rhs->get_root()) {
SASSERT(parent->is_true_eq());
unsigned expr_id = parent->get_owner_id();
bool_var v = get_bool_var_of_id(expr_id);
lbool val = get_assignment(v);
if (val != l_true) {
if (val == l_false && js.get_kind() == eq_justification::CONGRUENCE)
m_dyn_ack_manager.cg_conflict_eh(n1->get_owner(), n2->get_owner());
assign(literal(v), mk_justification(eq_propagation_justification(lhs, rhs)));
}
// It is not necessary to reinsert the equality to the congruence table
continue;
}
}
if (parent->is_cgc_enabled()) {
enode_bool_pair pair = m_cg_table.insert(parent);
enode * parent_prime = pair.first;
if (parent_prime == parent) {
TRACE("add_eq_parents", tout << "add_eq reinserting: #" << parent->get_owner_id() << "\n";);
SASSERT(parent);
SASSERT(parent->is_cgr());
SASSERT(m_cg_table.contains_ptr(parent));
r2_parents.push_back(parent);
continue;
}
parent->m_cg = parent_prime;
SASSERT(!m_cg_table.contains_ptr(parent));
if (parent_prime->m_root != parent->m_root) {
bool used_commutativity = pair.second;
TRACE("cg", tout << "found new congruence: #" << parent->get_owner_id() << " = #" << parent_prime->get_owner_id()
<< " used_commutativity: " << used_commutativity << "\n";);
push_new_congruence(parent, parent_prime, used_commutativity);
}
}
else {
// If congruence closure is not enabled for parent, then I just copy it
// to r2_parents
r2_parents.push_back(parent);
}
}
}
/**
\brief A transitivity 'proof' branch is represented by
the following sequence starting at n and ending
at n->get_root.
N1 = n
N_{i+1} = N_i->m_trans.m_target
and, there is an k such that N_k = n->get_root()
This method will invert this branch.
*/
void context::invert_trans(enode * n) {
enode * curr = n->m_trans.m_target;
enode * prev = n;
eq_justification js = n->m_trans.m_justification;
prev->m_trans.m_target = 0;
prev->m_trans.m_justification = null_eq_justification;
while (curr != 0) {
SASSERT(prev->trans_reaches(n));
enode * new_curr = curr->m_trans.m_target;
eq_justification new_js = curr->m_trans.m_justification;
curr->m_trans.m_target = prev;
curr->m_trans.m_justification = js;
prev = curr;
js = new_js;
curr = new_curr;
}
}
/**
\brief Given that r is the root of n, and r contains a theory variable
for theory th_id, this method returns a theory variable that is 'closer'
to n in the 'proof branch' n -> ... -> r.
This method is used to improve the quality of the conflict clauses produced
by the logical context.
Consider the following example:
- Consider the following sequence of equalities:
n1 = n2 = n3 = n4 = n5 = n6
- Now, assume that n1 is the root of the equivalence class
after each merge. So, the 'proof' branch will have the following
shape:
n1 <- n2 <- n3 <- n4 <- n5 <- n6
- Assuming that all nodes are attached to theory variable, then the following
sequence of equalities is sent to the theory if the method get_closest_var is not used:
n1 = n2, n1 = n3, n1 = n4, n1 = n5, n1 = n6
- This sequence is bad, and bad justifications may be produced by theory.
For example, assume the following arithmetic constraint
n5 < n6
For the arithmetic module, the best justification will be:
n1 = n5, n1 = n6 and n5 < n6
This justification contains unnecessary 'junk' to justify that n5 = n6.
That is, it proves n5 = n6 using the proofs for n1 = n5 and n1 = n6.
When the method get_closest_var is used in the communication with theories,
the logical context will send the natural sequence of equalities to the theories:
n1 = n2 = n3 = n4 = n5 = n6
*/
theory_var context::get_closest_var(enode * n, theory_id th_id) {
if (th_id == null_theory_id)
return null_theory_var;
while (n != 0) {
theory_var v = n->get_th_var(th_id);
if (v != null_theory_var)
return v;
n = n->m_trans.m_target;
}
return null_theory_var;
}
/**
\brief Merge the theory variables of n2->get_root() and n1->get_root(), the result is stored in n2->get_root().
New th_var equalities are propagated to the theories.
\remark In most cases, an enode is attached to at most one theory var.
*/
void context::merge_theory_vars(enode * n2, enode * n1, eq_justification js) {
enode * r2 = n2->get_root();
enode * r1 = n1->get_root();
if (!r1->has_th_vars() && !r2->has_th_vars())
return;
theory_id from_th = null_theory_id;
if (js.get_kind() == eq_justification::JUSTIFICATION)
from_th = js.get_justification()->get_from_theory();
if (r2->m_th_var_list.get_next() == 0 && r1->m_th_var_list.get_next() == 0) {
// Common case: r2 and r1 have at most one theory var.
theory_id t2 = r2->m_th_var_list.get_th_id();
theory_id t1 = r1->m_th_var_list.get_th_id();
// verbose_stream() << "[merge_theory_vars] t2: " << t2 << ", t1: " << t1 << "\n";
theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t2) : r2->m_th_var_list.get_th_var();
theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : r1->m_th_var_list.get_th_var();
TRACE("merge_theory_vars",
tout << "v2: " << v2 << " #" << r1->get_owner_id() << ", v1: " << v1 << " #" << r2->get_owner_id()
<< ", t2: " << t2 << ", t1: " << t1 << "\n";);
if (v2 != null_theory_var && v1 != null_theory_var) {
if (t1 == t2) {
// only send the equality to the theory, if the equality was not propagated by it.
if (t1 != from_th)
push_new_th_eq(t1, v2, v1);
}
else {
// uncommon case: r2 will have two theory_vars attached to it.
r2->add_th_var(v1, t1, m_region);
push_new_th_diseqs(r2, v1, get_theory(t1));
push_new_th_diseqs(r1, v2, get_theory(t2));
}
}
else if (v1 == null_theory_var && v2 != null_theory_var) {
push_new_th_diseqs(r1, v2, get_theory(t2));
}
else if (v1 != null_theory_var && v2 == null_theory_var) {
r2->m_th_var_list.set_th_var(v1);
r2->m_th_var_list.set_th_id(t1);
TRACE("merge_theory_vars", tout << "push_new_th_diseqs v1: " << v1 << ", t1: " << t1 << "\n";);
push_new_th_diseqs(r2, v1, get_theory(t1));
}
}
else {
// r1 and/or r2 have more than one theory variable.
TRACE("merge_theory_vars",
tout << "#" << r1->get_owner_id() << " == #" << r2->get_owner_id() << "\n";);
theory_var_list * l2 = r2->get_th_var_list();
while (l2) {
theory_id t2 = l2->get_th_id();
theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t2) : l2->get_th_var();
SASSERT(v2 != null_theory_var);
SASSERT(t2 != null_theory_id);
theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t2) : r1->get_th_var(t2);
if (v1 != null_theory_var) {
// only send the equality to the theory, if the equality was not propagated by it.
if (t2 != from_th)
push_new_th_eq(t2, v2, v1);
}
else {
push_new_th_diseqs(r1, v2, get_theory(t2));
}
l2 = l2->get_next();
}
theory_var_list * l1 = r1->get_th_var_list();
while (l1) {
theory_id t1 = l1->get_th_id();
theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : l1->get_th_var();
SASSERT(v1 != null_theory_var);
SASSERT(t1 != null_theory_id);
theory_var v2 = r2->get_th_var(t1);
if (v2 == null_theory_var) {
r2->add_th_var(v1, t1, m_region);
push_new_th_diseqs(r2, v1, get_theory(t1));
}
l1 = l1->get_next();
}
}
}
/**
\brief Propabate the boolean assignment when two equivalence classes are merged.
*/
void context::propagate_bool_enode_assignment(enode * r1, enode * r2, enode * n1, enode * n2) {
SASSERT(n1->is_bool());
SASSERT(n2->is_bool());
SASSERT(r1->is_bool());
SASSERT(r2->is_bool());
if (r2 == m_false_enode || r2 == m_true_enode) {
bool sign = r2 == m_false_enode;
enode * curr = r1;
do {
SASSERT(curr != m_false_enode);
bool_var v = enode2bool_var(curr);
literal l(v, sign);
if (get_assignment(l) != l_true)
assign(l, mk_justification(eq_root_propagation_justification(curr)));
curr = curr->m_next;
}
while(curr != r1);
}
else {
bool_var v1 = enode2bool_var(n1);
bool_var v2 = enode2bool_var(n2);
lbool val1 = get_assignment(v1);
lbool val2 = get_assignment(v2);
if (val1 != val2) {
if (val2 == l_undef)
propagate_bool_enode_assignment_core(n1, n2);
else
propagate_bool_enode_assignment_core(n2, n1);
}
}
}
/**
\brief source and target are boolean enodes, they were proved to be equal,
and the boolean variable associated with source is assigned. Then,
copy the assignment to the boolean variables associated with target.
*/
void context::propagate_bool_enode_assignment_core(enode * source, enode * target) {
SASSERT(source->is_bool());
SASSERT(target->is_bool());
SASSERT(source->get_root() == target->get_root());
bool_var v_source = enode2bool_var(source);
lbool val = get_assignment(v_source);
SASSERT(val != l_undef);
bool sign = val == l_false;
enode * first = target;
do {
bool_var v2 = enode2bool_var(target);
lbool val2 = get_assignment(v2);
if (val2 != val) {
if (val2 != l_undef && congruent(source, target) && source->get_num_args() > 0)
m_dyn_ack_manager.cg_conflict_eh(source->get_owner(), target->get_owner());
assign(literal(v2, sign), mk_justification(mp_iff_justification(source, target)));
}
target = target->get_next();
}
while (first != target);
}
void context::undo_add_eq(enode * r1, enode * n1, unsigned r2_num_parents) {
enode * r2 = r1->get_root();
TRACE("add_eq", tout << "undo_add_eq #" << r1->get_owner_id() << " #" << r2->get_owner_id() << "\n";);
// restore r2 class size
r2->m_class_size -= r1->m_class_size;
// unmerge "equivalence" classes
std::swap(r1->m_next, r2->m_next);
// remove the parents of r1 that remained as congruence roots
enode_vector::iterator it = r2->begin_parents();
enode_vector::iterator end = r2->end_parents();
it += r2_num_parents;
for (; it != end; ++it) {
enode * parent = *it;
if (parent->is_cgc_enabled()) {
TRACE("add_eq_parents", tout << "removing: #" << parent->get_owner_id() << "\n";);
CTRACE("add_eq", !parent->is_cgr(),
tout << "old num_parents: " << r2_num_parents << ", num_parents: " << r2->m_parents.size() << ", parent: #" <<
parent->get_owner_id() << ", parents: \n";
for (unsigned i = 0; i < r2->m_parents.size(); i++) {
tout << "#" << r2->m_parents[i]->get_owner_id() << " ";
}
display(tout););
SASSERT(parent->is_cgr());
SASSERT(m_cg_table.contains_ptr(parent));
m_cg_table.erase(parent);
}
}
enode * curr = r1;
do {
curr->m_root = r1;
curr = curr->m_next;
}
while(curr != r1);
// restore parents of r2
r2->m_parents.shrink(r2_num_parents);
// try to reinsert parents of r1 that are not cgr
it = r1->begin_parents();
end = r1->end_parents();
for (; it != end; ++it) {
enode * parent = *it;
TRACE("add_eq_parents", tout << "visiting: #" << parent->get_owner_id() << "\n";);
if (parent->is_cgc_enabled()) {
enode * cg = parent->m_cg;
if (!parent->is_true_eq() &&
(parent == cg || // parent was root of the congruence class before and after the merge
!congruent(parent, cg) // parent was root of the congruence class before but not after the merge
)) {
TRACE("add_eq_parents", tout << "trying to reinsert\n";);
m_cg_table.insert(parent);
parent->m_cg = parent;
}
}
}
// restore theory vars
if (r2->m_th_var_list.get_next() == 0) {
// common case: r2 has at most one variable
theory_var v2 = r2->m_th_var_list.get_th_var();
if (v2 != null_theory_var) {
theory_id t2 = r2->m_th_var_list.get_th_id();
if (get_theory(t2)->get_enode(v2)->get_root() != r2) {
SASSERT(get_theory(t2)->get_enode(v2)->get_root() == r1);
r2->m_th_var_list.set_th_var(null_theory_var); //remove variable from r2
r2->m_th_var_list.set_th_id(null_theory_id);
}
}
}
else {
restore_theory_vars(r2, r1);
}
// 'Proof' tree
// r1 -> .. -> n1 -> n2 -> ... -> r2
SASSERT(r1->trans_reaches(r2));
SASSERT(r1->trans_reaches(n1));
n1->m_trans.m_target = 0;
n1->m_trans.m_justification = null_eq_justification;
invert_trans(r1);
// ---------------
// n1 -> ... -> r1
// n2 -> ... -> r2
SASSERT(n1->trans_reaches(r1));
SASSERT(r1->m_trans.m_target == 0);
CASSERT("add_eq", check_invariant());
}
/**
\brief Auxiliary method for undo_add_eq.
It restores the theory variables of a given root enode.
This method deletes any theory variable v2 of r2 (for a theory t2)
whenever:
get_theory(t2)->get_enode(v2)->get_root() != r2
That is, v2 is not equivalent to r2 anymore.
*/
void context::restore_theory_vars(enode * r2, enode * r1) {
SASSERT(r2->get_root() == r2);
theory_var_list * new_l2 = 0;
theory_var_list * l2 = r2->get_th_var_list();
while (l2) {
theory_var v2 = l2->get_th_var();
theory_id t2 = l2->get_th_id();
if (get_theory(t2)->get_enode(v2)->get_root() != r2) {
SASSERT(get_theory(t2)->get_enode(v2)->get_root() == r1);
l2 = l2->get_next();
}
else {
if (new_l2) {
new_l2->set_next(l2);
new_l2 = l2;
}
else {
r2->m_th_var_list = *l2;
new_l2 = &(r2->m_th_var_list);
}
l2 = l2->get_next();
}
}
if (new_l2) {
new_l2->set_next(0);
}
else {
r2->m_th_var_list.set_th_var(null_theory_var);
r2->m_th_var_list.set_next(0);
}
}
/**
\brief This method is invoked when a new disequality is asserted.
The disequality is propagated to the theories.
*/
void context::add_diseq(enode * n1, enode * n2) {
enode * r1 = n1->get_root();
enode * r2 = n2->get_root();
TRACE("add_diseq", tout << "assigning: #" << n1->get_owner_id() << " != #" << n2->get_owner_id() << "\n";
tout << mk_ll_pp(n1->get_owner(), m_manager) << " != ";
tout << mk_ll_pp(n2->get_owner(), m_manager) << "\n";
tout << mk_ll_pp(r1->get_owner(), m_manager) << " != ";
tout << mk_ll_pp(r2->get_owner(), m_manager) << "\n";
);
#ifdef Z3DEBUG
push_trail(push_back_trail<context, enode_pair, false>(m_diseq_vector));
m_diseq_vector.push_back(enode_pair(n1, n2));
#endif
if (r1 == r2) {
TRACE("add_diseq_inconsistent", tout << "add_diseq #" << n1->get_owner_id() << " #" << n2->get_owner_id() << " inconsistency, scope_lvl: " << m_scope_lvl << "\n";);
return; // context is inconsistent
}
// Propagate disequalities to theories
if (r1->m_th_var_list.get_next() == 0 && r2->m_th_var_list.get_next() == 0) {
// common case: r2 and r1 have at most one theory var.
theory_id t1 = r1->m_th_var_list.get_th_id();
theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : r1->m_th_var_list.get_th_var();
theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t1) : r2->m_th_var_list.get_th_var();
TRACE("add_diseq", tout << "one theory diseq\n";
tout << v1 << " != " << v2 << "\n";
tout << "th1: " << t1 << " th2: " << r2->m_th_var_list.get_th_id() << "\n";
);
if (t1 != null_theory_id && v1 != null_theory_var && v2 != null_theory_var &&
t1 == r2->m_th_var_list.get_th_id()) {
if (get_theory(t1)->use_diseqs())
push_new_th_diseq(t1, v1, v2);
}
}
else {
theory_var_list * l1 = r1->get_th_var_list();
while (l1) {
theory_id t1 = l1->get_th_id();
theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : l1->get_th_var();
theory * th = get_theory(t1);
TRACE("add_diseq", tout << m_manager.get_family_name(t1) << "\n";);
if (th->use_diseqs()) {
theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t1) : r2->get_th_var(t1);
if (v2 != null_theory_var)
push_new_th_diseq(t1, v1, v2);
}
l1 = l1->get_next();
}
}
}
/**
\brief Return true if n1 and n2 are known to be disequal in the logical
context.
*/
bool context::is_diseq(enode * n1, enode * n2) const {
SASSERT(m_manager.get_sort(n1->get_owner()) == m_manager.get_sort(n2->get_owner()));
context * _this = const_cast<context*>(this);
if (!m_is_diseq_tmp) {
app * eq = m_manager.mk_eq(n1->get_owner(), n2->get_owner());
m_manager.inc_ref(eq);
_this->m_is_diseq_tmp = enode::mk_dummy(m_manager, m_app2enode, eq);
}
else if (m_manager.get_sort(m_is_diseq_tmp->get_owner()->get_arg(0)) != m_manager.get_sort(n1->get_owner())) {
m_manager.dec_ref(m_is_diseq_tmp->get_owner());
app * eq = m_manager.mk_eq(n1->get_owner(), n2->get_owner());
m_manager.inc_ref(eq);
m_is_diseq_tmp->m_func_decl_id = UINT_MAX;
m_is_diseq_tmp->m_owner = eq;
}
m_is_diseq_tmp->m_args[0] = n1;
m_is_diseq_tmp->m_args[1] = n2;
SASSERT(m_is_diseq_tmp->get_num_args() == 2);
enode * r = m_cg_table.find(m_is_diseq_tmp);
SASSERT((r != 0) == m_cg_table.contains(m_is_diseq_tmp));
TRACE("is_diseq", tout << "r: " << r << "\n";);
if (r) {
SASSERT(r->is_eq());
literal l = enode2literal(r->get_root());
// SASSERT(result == is_diseq_slow(n1, n2));
return l == false_literal || (is_relevant(l) && get_assignment(l) == l_false);
}
CTRACE("is_diseq_bug", is_diseq_slow(n1, n2), tout << "#" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n";);
return false;
}
/**
\brief Slow version of is_diseq
*/
bool context::is_diseq_slow(enode * n1, enode * n2) const {
if (n1->get_num_parents() > n2->get_num_parents())
std::swap(n1, n2);
enode_vector::iterator it = n1->begin_parents();
enode_vector::iterator end = n1->end_parents();
for (; it != end; ++it) {
enode * parent = *it;
if (parent->is_eq() && is_relevant(parent->get_owner()) && get_assignment(enode2bool_var(parent)) == l_false &&
((parent->get_arg(0)->get_root() == n1->get_root() && parent->get_arg(1)->get_root() == n2->get_root()) ||
(parent->get_arg(1)->get_root() == n1->get_root() && parent->get_arg(0)->get_root() == n2->get_root()))) {
TRACE("is_diseq_bug", tout << "parent: #" << parent->get_owner_id() << ", parent->root: #" <<
parent->get_root()->get_owner_id() << " assignment(parent): " << get_assignment(enode2bool_var(parent)) <<
" args: #" << parent->get_arg(0)->get_owner_id() << " #" << parent->get_arg(1)->get_owner_id() << "\n";);
return true;
}
}
return false;
}
#define SMALL_NUM_PARENTS 3
bool context::is_ext_diseq(enode * n1, enode * n2, unsigned depth) {
enode * r1 = n1->get_root();
enode * r2 = n2->get_root();
if (r1 == r2)
return false;
if (r1->is_interpreted() && r2->is_interpreted())
return true;
if (is_diseq(n1, n2))
return true;
if (r1->get_num_parents() > r2->get_num_parents()) {
std::swap(n1, n2);
std::swap(r1, r2);
}
if (depth == 0)
return false;
if (r1->get_num_parents() < SMALL_NUM_PARENTS) {
TRACE("is_ext_diseq", tout << mk_bounded_pp(n1->get_owner(), m_manager) << " " << mk_bounded_pp(n2->get_owner(), m_manager) << " " << depth << "\n";);
enode_vector::iterator it1 = r1->begin_parents();
enode_vector::iterator end1 = r1->end_parents();
for (; it1 != end1; ++it1) {
enode * p1 = *it1;
if (!is_relevant(p1))
continue;
if (p1->is_eq())
continue;
if (!p1->is_cgr())
continue;
func_decl * f = p1->get_decl();
TRACE("is_ext_diseq", tout << "p1: " << mk_bounded_pp(p1->get_owner(), m_manager) << "\n";);
unsigned num_args = p1->get_num_args();
enode_vector::iterator it2 = r2->begin_parents();
enode_vector::iterator end2 = r2->end_parents();
for (; it2 != end2; ++it2) {
enode * p2 = *it2;
if (!is_relevant(p2))
continue;
if (p2->is_eq())
continue;
if (!p2->is_cgr())
continue;
TRACE("is_ext_diseq", tout << "p2: " << mk_bounded_pp(p2->get_owner(), m_manager) << "\n";);
if (p1->get_root() != p2->get_root() && p2->get_decl() == f && p2->get_num_args() == num_args) {
unsigned j = 0;
for (j = 0; j < num_args; j++) {
enode * arg1 = p1->get_arg(j)->get_root();
enode * arg2 = p2->get_arg(j)->get_root();
if (arg1 == arg2)
continue;
if ((arg1 == r1 || arg1 == r2) &&
(arg2 == r1 || arg2 == r2))
continue;
break;
}
if (j == num_args) {
TRACE("is_ext_diseq", tout << "found parents: " << mk_bounded_pp(p1->get_owner(), m_manager) << " " << mk_bounded_pp(p2->get_owner(), m_manager) << "\n";);
if (is_ext_diseq(p1, p2, depth - 1))
return true;
}
}
}
}
}
else {
if (depth >= m_almost_cg_tables.size()) {
unsigned old_sz = m_almost_cg_tables.size();
m_almost_cg_tables.resize(depth+1, 0);
for (unsigned i = old_sz; i < depth + 1; i++)
m_almost_cg_tables[i] = alloc(almost_cg_table);
}
almost_cg_table & table = *(m_almost_cg_tables[depth]);
table.reset(r1, r2);
enode_vector::iterator it1 = r1->begin_parents();
enode_vector::iterator end1 = r1->end_parents();
for (; it1 != end1; ++it1) {
enode * p1 = *it1;
if (!is_relevant(p1))
continue;
if (p1->is_eq())
continue;
if (!p1->is_cgr())
continue;
table.insert(p1);
}
if (table.empty())
return false;
enode_vector::iterator it2 = r2->begin_parents();
enode_vector::iterator end2 = r2->end_parents();
for (; it2 != end2; ++it2) {
enode * p2 = *it2;
if (!is_relevant(p2))
continue;
if (p2->is_eq())
continue;
if (!p2->is_cgr())
continue;
list<enode*> * ps = table.find(p2);
if (ps) {
while (ps) {
enode * p1 = ps->head();
if (p1->get_root() != p2->get_root() && is_ext_diseq(p1, p2, depth - 1))
return true;
ps = ps->tail();
}
}
}
}
return false;
}
/**
\brief Return an enode n congruent to (f args). If there is no such enode in the E-graph, then return 0.
*/
enode * context::get_enode_eq_to(func_decl * f, unsigned num_args, enode * const * args) {
enode * tmp = m_tmp_enode.set(f, num_args, args);
enode * r = m_cg_table.find(tmp);
#ifdef Z3DEBUG
if (r != 0) {
SASSERT(r->get_owner()->get_decl() == f);
SASSERT(r->get_num_args() == num_args);
if (r->is_commutative()) {
// TODO
}
else {
for (unsigned i = 0; i < num_args; i++) {
expr * arg = r->get_owner()->get_arg(i);
SASSERT(e_internalized(arg));
enode * _arg = get_enode(arg);
CTRACE("eq_to_bug", args[i]->get_root() != _arg->get_root(),
tout << "#" << args[i]->get_owner_id() << " #" << args[i]->get_root()->get_owner_id()
<< " #" << _arg->get_owner_id() << " #" << _arg->get_root()->get_owner_id() << "\n";
tout << "#" << r->get_owner_id() << "\n";
display(tout););
SASSERT(args[i]->get_root() == _arg->get_root());
}
}
}
#endif
return r;
}
/**
\brief Process the equality propagation queue.
\remark The method assign_eq adds a new entry on this queue.
*/
bool context::propagate_eqs() {
for (unsigned i = 0; i < m_eq_propagation_queue.size(); i++) {
new_eq & entry = m_eq_propagation_queue[i];
#if 0
static unsigned counter1 = 0;
static unsigned counter2 = 0;
if (entry.m_lhs->is_eq() || entry.m_rhs->is_eq())
counter1++;
else
counter2++;
if ((counter1 + counter2) % 10000 == 0)
std::cout << counter1 << " " << counter2 << "\n";
#endif
add_eq(entry.m_lhs, entry.m_rhs, entry.m_justification);
if (inconsistent())
return false;
}
m_eq_propagation_queue.reset();
return true;
}
/**
\brief Process equalities, theory atoms, etc.
*/
bool context::propagate_atoms() {
SASSERT(!inconsistent());
for (unsigned i = 0; i < m_atom_propagation_queue.size(); i++) {
SASSERT(!inconsistent());
literal l = m_atom_propagation_queue[i];
bool_var v = l.var();
bool_var_data & d = get_bdata(v);
lbool val = get_assignment(v);
TRACE("propagate_atoms", tout << "propagating atom, #" << bool_var2expr(v)->get_id() << ", is_enode(): " << d.is_enode() << " " << l << "\n";);
SASSERT(val != l_undef);
if (d.is_enode())
propagate_bool_var_enode(v);
if (inconsistent())
return false;
if (d.is_eq()) {
app * n = to_app(m_bool_var2expr[v]);
SASSERT(m_manager.is_eq(n));
expr * lhs = n->get_arg(0);
expr * rhs = n->get_arg(1);
if (val == l_true) {
add_eq(get_enode(lhs), get_enode(rhs), eq_justification(l));
}
else {
TRACE("add_diseq", display_eq_detail(tout, bool_var2enode(v)););
add_diseq(get_enode(lhs), get_enode(rhs));
}
}
else if (d.is_theory_atom()) {
theory * th = m_theories.get_plugin(d.get_theory());
SASSERT(th);
th->assign_eh(v, val == l_true);
}
else if (d.is_quantifier()) {
// Remark: when RELEVANCY_LEMMA is true, a quantifier can be asserted to false and marked as relevant.
// This happens when a quantifier is part of a lemma (conflict-clause), and this conflict clause
// becomes an unit-clause and the remaining literal is the negation of a quantifier.
CTRACE("assign_quantifier_bug", get_assignment(v) != l_true,
tout << "#" << bool_var2expr(v)->get_id() << " val: " << get_assignment(v) << "\n";
tout << mk_pp(bool_var2expr(v), m_manager) << "\n";
display(tout););
SASSERT(is_quantifier(m_bool_var2expr[v]));
if (get_assignment(v) == l_true) {
// All universal quantifiers have positive polarity in the input formula.
// So, we can ignore quantifiers assigned to false.
assign_quantifier(to_quantifier(m_bool_var2expr[v]));
}
}
if (inconsistent())
return false;
}
m_atom_propagation_queue.reset();
return true;
}
class set_var_theory_trail : public trail<context> {
bool_var m_var;
public:
set_var_theory_trail(bool_var v):m_var(v) {}
virtual void undo(context & ctx) {
bool_var_data & d = ctx.m_bdata[m_var];
d.reset_notify_theory();
}
};
void context::set_var_theory(bool_var v, theory_id tid) {
SASSERT(get_var_theory(v) == null_theory_var);
SASSERT(tid > 0 && tid <= 255);
SASSERT(get_intern_level(v) <= m_scope_lvl);
if (m_scope_lvl > get_intern_level(v))
push_trail(set_var_theory_trail(v));
bool_var_data & d = m_bdata[v];
d.set_notify_theory(tid);
}
/**
\brief Propagate the truth value assigned to v, to the enode
associated with v. Let n be the enode associated with v. Then,
this method merges n = true_term (n = false_term) if v was
assigned to true (false).
*/
void context::propagate_bool_var_enode(bool_var v) {
SASSERT(get_assignment(v) != l_undef);
SASSERT(get_bdata(v).is_enode());
lbool val = get_assignment(v);
TRACE("propagate_bool_var_enode_bug", tout << "var: " << v << " #" << bool_var2expr(v)->get_id() << "\n";);
SASSERT(v < static_cast<int>(m_b_internalized_stack.size()));
enode * n = bool_var2enode(v);
bool sign = val == l_false;
if (n->merge_tf())
add_eq(n, sign ? m_false_enode : m_true_enode, eq_justification(literal(v, sign)));
enode * r = n->get_root();
if (r == m_true_enode || r == m_false_enode)
return;
// Move truth value to other elements of the equivalence class if:
// 1) n is the root of the equivalence class
// 2) n is not the root, but the variable associated with the root is unassigned.
if (n == r ||
!is_relevant(r) || // <<<< added to fix propagation bug.
get_assignment(enode2bool_var(r)) != val) {
enode * first = n;
n = n->get_next();
while (n != first) {
bool_var v2 = enode2bool_var(n);
if (get_assignment(v2) != val)
assign(literal(v2, sign), mk_justification(mp_iff_justification(first, n)));
n = n->get_next();
}
}
}
/**
\brief Traverse the disequalities of r's equivalence class, and
propagate them to the theory.
*/
void context::push_new_th_diseqs(enode * r, theory_var v, theory * th) {
if (!th->use_diseqs()) {
TRACE("push_new_th_diseqs", tout << m_manager.get_family_name(th->get_id()) << " not using diseqs\n";);
return;
}
TRACE("push_new_th_diseqs", tout << "#" << r->get_owner_id() << " v" << v << "\n";);
theory_id th_id = th->get_id();
enode_vector::iterator it = r->begin_parents();
enode_vector::iterator end = r->end_parents();
for (; it != end; ++it) {
enode * parent = *it;
CTRACE("parent_bug", parent == 0, tout << "#" << r->get_owner_id() << ", num_parents: " << r->get_num_parents() << "\n"; display(tout););
if (parent->is_eq()) {
bool_var bv = get_bool_var_of_id(parent->get_owner_id());
if (get_assignment(bv) == l_false) {
enode * lhs = parent->get_arg(0);
enode * rhs = parent->get_arg(1);
TRACE("push_new_th_diseqs",
tout << "#" << parent->get_owner_id() << " ";
tout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() <<
", lhs->root: #" << lhs->get_root()->get_owner_id() << ", rhs->root: #" << rhs->get_root()->get_owner_id() <<
", r: #" << r->get_owner_id() << ", r->root: #" << r->get_root()->get_owner_id() << "\n";
);
CTRACE("push_new_th_diseqs", lhs->get_root() != r->get_root() && rhs->get_root() != r->get_root(),
tout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() <<
", lhs->root: #" << lhs->get_root()->get_owner_id() << ", rhs->root: #" << rhs->get_root()->get_owner_id() <<
", r: #" << r->get_owner_id() << ", r->root: #" << r->get_root()->get_owner_id() << "\n";
display(tout););
SASSERT(lhs->get_root() == r->get_root() || rhs->get_root() == r->get_root());
if (rhs->get_root() == r->get_root())
std::swap(lhs, rhs);
enode * rhs_root = rhs->get_root();
theory_var rhs_var = m_fparams.m_new_core2th_eq ? get_closest_var(rhs, th_id) : rhs_root->get_th_var(th_id);
if (m_fparams.m_new_core2th_eq) {
theory_var _v = get_closest_var(lhs, th_id);
if (_v != null_theory_var)
v = _v;
}
if (rhs_var != null_theory_var && v != rhs_var /* if v == rhs_var then the context will detect the inconsistency. */)
push_new_th_diseq(th_id, v, rhs_var);
}
}
}
}
/**
\brief Return the truth assignment for an expression
that is attached to a boolean variable.
\pre The expression must be attached to a boolean variable.
*/
inline lbool context::get_assignment_core(expr * n) const {
CTRACE("get_assignment_bug", !b_internalized(n), tout << "n:\n" << mk_pp(n, m_manager) << "\n"; display(tout););
SASSERT(b_internalized(n));
unsigned id = n->get_id();
bool_var var = get_bool_var_of_id(id);
SASSERT(var != null_bool_var);
return get_assignment(var);
}
/**
\brief Return the truth assignment for an expression.
If the expression is a not-application, then its child
is inspected instead.
*/
lbool context::get_assignment(expr * n) const {
if (m_manager.is_false(n))
return l_false;
if (m_manager.is_not(n))
return ~get_assignment_core(to_app(n)->get_arg(0));
return get_assignment_core(n);
}
lbool context::find_assignment(expr * n) const {
if (m_manager.is_false(n))
return l_false;
if (m_manager.is_not(n)) {
expr * arg = to_app(n)->get_arg(0);
if (b_internalized(arg))
return ~get_assignment_core(arg);
return l_undef;
}
if (b_internalized(n))
return get_assignment(n);
return l_undef;
}
/**
\brief Return the assignment of a 'boolean' enode.
If the enode is not boolean, then return l_undef.
*/
lbool context::get_assignment(enode * n) const {
expr * owner = n->get_owner();
if (!m_manager.is_bool(owner))
return l_undef;
if (n == m_false_enode)
return l_false;
bool_var v = get_bool_var_of_id(owner->get_id());
CTRACE("missing_propagation", v == null_bool_var, tout << mk_pp(owner, m_manager) << "\n";);
return get_assignment(v);
}
/**
\brief Return set of assigned literals as expressions.
*/
void context::get_assignments(expr_ref_vector& assignments) {
literal_vector::const_iterator it = m_assigned_literals.begin();
literal_vector::const_iterator end = m_assigned_literals.end();
for (; it != end; ++it) {
expr_ref e(m_manager);
literal2expr(*it, e);
assignments.push_back(e);
}
}
void context::relevant_eh(expr * n) {
if (b_internalized(n)) {
bool_var v = get_bool_var(n);
bool_var_data & d = get_bdata(v);
SASSERT(relevancy());
// Quantifiers are only asserted when marked as relevant.
// Other atoms are only asserted when marked as relevant if m_relevancy_lvl >= 2
if (d.is_atom() && (d.is_quantifier() || m_fparams.m_relevancy_lvl >= 2)) {
lbool val = get_assignment(v);
if (val != l_undef)
m_atom_propagation_queue.push_back(literal(v, val == l_false));
}
}
TRACE("propagate_relevancy", tout << "marking as relevant:\n" << mk_bounded_pp(n, m_manager) << "\n";);
#ifndef SMTCOMP
m_case_split_queue->relevant_eh(n);
#endif
if (is_app(n)) {
if (e_internalized(n)) {
SASSERT(relevancy());
enode * e = get_enode(n);
m_qmanager->relevant_eh(e);
}
theory * propagated_th = 0;
family_id fid = to_app(n)->get_family_id();
if (fid != m_manager.get_basic_family_id()) {
theory * th = get_theory(fid);
if (th != 0) {
th->relevant_eh(to_app(n));
propagated_th = th; // <<< mark that relevancy_eh was already invoked for theory th.
}
}
if (e_internalized(n)) {
enode * e = get_enode(n);
theory_var_list * l = e->get_th_var_list();
while (l) {
theory_id th_id = l->get_th_id();
theory * th = get_theory(th_id);
// I don't want to invoke relevant_eh twice for the same n.
if (th != propagated_th)
th->relevant_eh(to_app(n));
l = l->get_next();
}
}
}
}
/**
\brief Propagate relevancy using the queue of new assigned literals
located at [qhead, m_assigned_literals.size()).
*/
void context::propagate_relevancy(unsigned qhead) {
if (!relevancy())
return;
unsigned sz = m_assigned_literals.size();
while (qhead < sz) {
literal l = m_assigned_literals[qhead];
SASSERT(get_assignment(l) == l_true);
qhead++;
bool_var var = l.var();
expr * n = m_bool_var2expr[var];
m_relevancy_propagator->assign_eh(n, !l.sign());
}
m_relevancy_propagator->propagate();
}
bool context::propagate_theories() {
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it) {
(*it)->propagate();
if (inconsistent())
return false;
}
return true;
}
void context::propagate_th_eqs() {
for (unsigned i = 0; i < m_th_eq_propagation_queue.size() && !inconsistent(); i++) {
new_th_eq curr = m_th_eq_propagation_queue[i];
theory * th = get_theory(curr.m_th_id);
SASSERT(th);
th->new_eq_eh(curr.m_lhs, curr.m_rhs);
#ifdef Z3DEBUG
push_trail(push_back_trail<context, new_th_eq, false>(m_propagated_th_eqs));
m_propagated_th_eqs.push_back(curr);
#endif
}
m_th_eq_propagation_queue.reset();
}
void context::propagate_th_diseqs() {
for (unsigned i = 0; i < m_th_diseq_propagation_queue.size() && !inconsistent(); i++) {
new_th_eq curr = m_th_diseq_propagation_queue[i];
theory * th = get_theory(curr.m_th_id);
SASSERT(th);
th->new_diseq_eh(curr.m_lhs, curr.m_rhs);
#ifdef Z3DEBUG
push_trail(push_back_trail<context, new_th_eq, false>(m_propagated_th_diseqs));
m_propagated_th_diseqs.push_back(curr);
#endif
}
m_th_diseq_propagation_queue.reset();
}
bool context::can_theories_propagate() const {
ptr_vector<theory>::const_iterator it = m_theory_set.begin();
ptr_vector<theory>::const_iterator end = m_theory_set.end();
for (; it != end; ++it) {
if ((*it)->can_propagate()) {
return true;
}
}
return false;
}
bool context::can_propagate() const {
return
m_qhead != m_assigned_literals.size() ||
m_relevancy_propagator->can_propagate() ||
!m_atom_propagation_queue.empty() ||
m_qmanager->can_propagate() ||
can_theories_propagate() ||
!m_eq_propagation_queue.empty() ||
!m_th_eq_propagation_queue.empty() ||
!m_th_diseq_propagation_queue.empty();
}
bool context::propagate() {
TRACE("propagate", tout << "propagating...\n";);
while (true) {
if (inconsistent())
return false;
unsigned qhead = m_qhead;
if (!bcp())
return false;
SASSERT(!inconsistent());
propagate_relevancy(qhead);
if (inconsistent())
return false;
if (!propagate_atoms())
return false;
if (!propagate_eqs())
return false;
propagate_th_eqs();
propagate_th_diseqs();
if (inconsistent())
return false;
if (!propagate_theories())
return false;
m_qmanager->propagate();
if (inconsistent())
return false;
if (resource_limits_exceeded())
return true;
if (!can_propagate()) {
CASSERT("diseq_bug", inconsistent() || check_missing_diseq_conflict());
CASSERT("eqc_bool", check_eqc_bool_assignment());
return true;
}
}
}
void context::set_conflict(b_justification js, literal not_l) {
if (!inconsistent()) {
m_conflict = js;
m_not_l = not_l;
}
}
void context::assign_quantifier(quantifier * q) {
TRACE("assumption", tout << mk_pp(q, m_manager) << "\n";);
m_qmanager->assign_eh(q);
}
bool context::contains_instance(quantifier * q, unsigned num_bindings, enode * const * bindings) {
return m_fingerprints.contains(q, q->get_id(), num_bindings, bindings);
}
bool context::add_instance(quantifier * q, app * pat, unsigned num_bindings, enode * const * bindings, unsigned max_generation,
unsigned min_top_generation, unsigned max_top_generation, ptr_vector<enode> & used_enodes) {
return m_qmanager->add_instance(q, pat, num_bindings, bindings, max_generation, min_top_generation, max_top_generation, used_enodes);
}
void context::rescale_bool_var_activity() {
svector<double>::iterator it = m_activity.begin();
svector<double>::iterator end = m_activity.end();
for (; it != end; ++it)
*it *= INV_ACTIVITY_LIMIT;
m_bvar_inc *= INV_ACTIVITY_LIMIT;
}
/**
\brief Execute next clase split, return false if there are no
more case splits to be performed.
*/
bool context::decide() {
bool_var var;
lbool phase;
m_case_split_queue->next_case_split(var, phase);
if (var == null_bool_var) {
return false;
}
TRACE_CODE({
static unsigned counter = 0;
counter++;
if (counter % 100 == 0) {
TRACE("activity_profile",
for (unsigned i=0; i<get_num_bool_vars(); i++) {
tout << get_activity(i) << " ";
}
tout << "\n";);
}});
m_stats.m_num_decisions++;
push_scope();
TRACE("context", tout << "splitting, lvl: " << m_scope_lvl << "\n";);
TRACE("decide_detail", tout << mk_pp(bool_var2expr(var), m_manager) << "\n";);
bool is_pos;
if (is_quantifier(m_bool_var2expr[var])) {
// Overriding any decision on how to assign the quantifier.
// assigining a quantifier to false is equivalent to make it irrelevant.
phase = l_false;
}
if (phase != l_undef) {
is_pos = phase == l_true;
}
else {
bool_var_data & d = m_bdata[var];
if (d.try_true_first()) {
is_pos = true;
}
else {
switch (m_fparams.m_phase_selection) {
case PS_CACHING:
case PS_CACHING_CONSERVATIVE:
case PS_CACHING_CONSERVATIVE2:
if (m_phase_cache_on && d.m_phase_available) {
TRACE("phase_selection", tout << "using cached value, is_pos: " << m_bdata[var].m_phase << ", var: p" << var << "\n";);
is_pos = m_bdata[var].m_phase;
}
else {
TRACE("phase_selection", tout << "setting to false\n";);
is_pos = m_phase_default;
}
break;
case PS_ALWAYS_FALSE:
is_pos = false;
break;
case PS_ALWAYS_TRUE:
is_pos = true;
break;
case PS_RANDOM:
is_pos = (m_random() % 2 == 0);
break;
case PS_OCCURRENCE: {
literal l(var);
is_pos = m_lit_occs[l.index()].size() > m_lit_occs[(~l).index()].size();
break;
}
default:
is_pos = false;
UNREACHABLE();
}
}
}
TRACE("decide", tout << "case split pos: " << is_pos << " p" << var << "\n"
<< "activity: " << get_activity(var) << "\n";);
assign(literal(var, !is_pos), b_justification::mk_axiom(), true);
return true;
}
/**
\brief Update counter that is used to enable/disable phase caching.
*/
void context::update_phase_cache_counter() {
m_phase_counter++;
if (m_phase_cache_on) {
if (m_phase_counter >= m_fparams.m_phase_caching_on) {
m_phase_counter = 0;
m_phase_cache_on = false;
if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2)
m_phase_default = !m_phase_default;
}
}
else {
if (m_phase_counter >= m_fparams.m_phase_caching_off) {
m_phase_counter = 0;
m_phase_cache_on = true;
if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2)
m_phase_default = !m_phase_default;
}
}
}
/**
\brief Create an internal backtracking point
*/
void context::push_scope() {
#ifndef SMTCOMP
if (m_fparams.m_trace_stream != NULL)
*m_fparams.m_trace_stream << "[push] " << m_scope_lvl << "\n";
#endif
m_scope_lvl++;
m_region.push_scope();
m_scopes.push_back(scope());
scope & s = m_scopes.back();
m_relevancy_propagator->push();
s.m_assigned_literals_lim = m_assigned_literals.size();
s.m_trail_stack_lim = m_trail_stack.size();
s.m_aux_clauses_lim = m_aux_clauses.size();
s.m_justifications_lim = m_justifications.size();
s.m_units_to_reassert_lim = m_units_to_reassert.size();
m_qmanager->push();
m_fingerprints.push_scope();
m_case_split_queue->push_scope();
m_asserted_formulas.push_scope();
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it)
(*it)->push_scope_eh();
CASSERT("context", check_invariant());
}
/**
\brief Execute generic undo-objects.
*/
void context::undo_trail_stack(unsigned old_size) {
::undo_trail_stack(*this, m_trail_stack, old_size);
}
/**
\brief Remove watch literal idx from the given clause.
\pre idx must be 0 or 1.
*/
void context::remove_watch_literal(clause * cls, unsigned idx) {
m_watches[(~cls->get_literal(idx)).index()].remove_clause(cls);
}
/**
\brief Update the index used for backward subsumption.
*/
void context::remove_lit_occs(clause * cls) {
unsigned num_lits = cls->get_num_literals();
for (unsigned i = 0; i < num_lits; i++) {
literal l = cls->get_literal(i);
m_lit_occs[l.index()].erase(cls);
}
}
void context::remove_cls_occs(clause * cls) {
remove_watch_literal(cls, 0);
remove_watch_literal(cls, 1);
if (lit_occs_enabled())
remove_lit_occs(cls);
}
/**
\brief Delete the given clause.
\pre Clause is not in the reinit stack.
*/
void context::del_clause(clause * cls) {
SASSERT(m_flushing || !cls->in_reinit_stack());
if (!cls->deleted())
remove_cls_occs(cls);
cls->deallocate(m_manager);
m_stats.m_num_del_clause++;
}
/**
\brief Delete the clauses in v at locations [old_size .. v.size())
Reduce the size of v to old_size.
*/
void context::del_clauses(clause_vector & v, unsigned old_size) {
SASSERT(old_size <= v.size());
clause_vector::iterator begin = v.begin() + old_size;
clause_vector::iterator it = v.end();
while (it != begin) {
--it;
del_clause(*it);
}
v.shrink(old_size);
}
void context::mark_as_deleted(clause * cls) {
SASSERT(!cls->deleted());
remove_cls_occs(cls);
cls->mark_as_deleted(m_manager);
}
/**
\brief Undo variable assignments.
*/
void context::unassign_vars(unsigned old_lim) {
SASSERT(old_lim <= m_assigned_literals.size());
unsigned i = m_assigned_literals.size();
while (i != old_lim) {
--i;
literal l = m_assigned_literals[i];
m_assignment[l.index()] = l_undef;
m_assignment[(~l).index()] = l_undef;
bool_var v = l.var();
bool_var_data & d = get_bdata(v);
d.m_justification = null_b_justification;
m_case_split_queue->unassign_var_eh(v);
}
m_assigned_literals.shrink(old_lim);
m_qhead = old_lim;
SASSERT(m_qhead == m_assigned_literals.size());
}
/**
\brief Invoke method del_eh for the justification that will be deleted.
If the method in_region() returns false, then delete operator is invoked.
*/
void context::del_justifications(ptr_vector<justification> & justifications, unsigned old_lim) {
SASSERT(old_lim <= justifications.size());
unsigned i = justifications.size();
while (i != old_lim) {
--i;
justification * js = justifications[i];
js->del_eh(m_manager);
if (!js->in_region()) {
dealloc(js);
}
else {
// If the justification is in a region, then explicitly invoke the destructor.
// This is needed because some justification objects contains vectors.
// The destructors of these vectors need to be invoked.
js->~justification();
}
}
justifications.shrink(old_lim);
}
/**
\brief Return true if all literals of c are assigned to false.
*/
bool context::is_empty_clause(clause const * c) const {
unsigned num_lits = c->get_num_literals();
for(unsigned i = 0; i < num_lits; i++) {
literal l = c->get_literal(i);
if (get_assignment(l) != l_false)
return false;
}
return true;
}
/**
\brief Return true if the given clause contains one and only one unassigned literal.
*/
bool context::is_unit_clause(clause const * c) const {
bool found = false;
unsigned num_lits = c->get_num_literals();
for(unsigned i = 0; i < num_lits; i++) {
literal l = c->get_literal(i);
switch (get_assignment(l)) {
case l_false:
break; // skip
case l_undef:
if (found)
return false;
else
found = true;
break;
case l_true:
return false; // clause is already satisfied.
}
}
return found;
}
/**
\brief When a clause is reinitialized (see reinit_clauses) enodes and literals may
need to be recreated. When an enode is recreated, I want to use the same generation
number it had before being deleted. Otherwise the generation will be 0, and will affect
the loop prevetion heuristics used to control quantifier instantiation.
Thus, I cache the generation number of enodes that will be deleted during backtracking
and recreated by reinit_clauses.
*/
void context::cache_generation(unsigned new_scope_lvl) {
if (!m_clauses_to_reinit.empty()) {
unsigned lim = m_scope_lvl;
if (m_clauses_to_reinit.size() <= lim) {
SASSERT(!m_clauses_to_reinit.empty());
lim = m_clauses_to_reinit.size() - 1;
}
for (unsigned i = new_scope_lvl; i <= lim; i++) {
clause_vector & v = m_clauses_to_reinit[i];
clause_vector::iterator it = v.begin();
clause_vector::iterator end = v.end();
for (; it != end; ++it) {
clause * cls = *it;
cache_generation(cls, new_scope_lvl);
}
}
}
if (!m_units_to_reassert.empty()) {
scope & s = m_scopes[new_scope_lvl];
unsigned i = s.m_units_to_reassert_lim;
unsigned sz = m_units_to_reassert.size();
for (; i < sz; i++) {
expr * unit = m_units_to_reassert.get(i);
cache_generation(unit, new_scope_lvl);
}
}
}
/**
\brief See cache_generation(unsigned new_scope_lvl)
*/
void context::cache_generation(clause const * cls, unsigned new_scope_lvl) {
cache_generation(cls->get_num_literals(), cls->begin_literals(), new_scope_lvl);
}
/**
\brief See cache_generation(unsigned new_scope_lvl)
*/
void context::cache_generation(unsigned num_lits, literal const * lits, unsigned new_scope_lvl) {
for(unsigned i = 0; i < num_lits; i++) {
bool_var v = lits[i].var();
unsigned ilvl = get_intern_level(v);
if (ilvl > new_scope_lvl)
cache_generation(bool_var2expr(v), new_scope_lvl);
}
}
/**
\brief See cache_generation(unsigned new_scope_lvl)
*/
void context::cache_generation(expr * n, unsigned new_scope_lvl) {
ptr_buffer<expr> todo;
todo.push_back(n);
while (!todo.empty()) {
expr * n = todo.back();
todo.pop_back();
if (m_cache_generation_visited.contains(n))
continue;
m_cache_generation_visited.insert(n);
if (is_app(n)) {
if (e_internalized(n)) {
enode * e = get_enode(n);
unsigned ilvl = e->get_iscope_lvl();
if (ilvl <= new_scope_lvl)
continue; // node and its children will not be recreated during backtracking
TRACE("cached_generation", tout << "caching: #" << n->get_id() << " " << e->get_generation() << "\n";);
m_cached_generation.insert(n, e->get_generation());
}
unsigned num_args = to_app(n)->get_num_args();
for (unsigned i = 0; i < num_args; i++) {
expr * arg = to_app(n)->get_arg(i);
if (is_app(arg) || is_quantifier(arg))
todo.push_back(arg);
}
}
else if (is_quantifier(n) && b_internalized(n)) {
m_cached_generation.insert(n, m_qmanager->get_generation(to_quantifier(n)));
todo.push_back(to_quantifier(n)->get_expr());
}
}
}
/**
\brief See cache_generation(unsigned new_scope_lvl)
*/
void context::reset_cache_generation() {
m_cache_generation_visited.reset();
m_cached_generation.reset();
}
/**
\brief Reinitialize learned clauses (lemmas) that contain boolean variables
that were deleted during backtracking.
\remark num_bool_vars contains the number of boolean variables alive
after backtracking. So, a clause contains a dead variable if it
contains a literal l where l.var() >= num_bool_vars.
*/
void context::reinit_clauses(unsigned num_scopes, unsigned num_bool_vars) {
TRACE("reinit_clauses_bug", display_watch_lists(tout););
if (m_clauses_to_reinit.empty())
return;
unsigned lim = m_scope_lvl + num_scopes;
if (m_clauses_to_reinit.size() <= lim) {
SASSERT(!m_clauses_to_reinit.empty());
lim = m_clauses_to_reinit.size() - 1;
}
for (unsigned i = m_scope_lvl+1; i <= lim; i++) {
clause_vector & v = m_clauses_to_reinit[i];
clause_vector::iterator it = v.begin();
clause_vector::iterator end = v.end();
for (; it != end; ++it) {
clause * cls = *it;
if (cls->deleted()) {
cls->release_atoms(m_manager);
cls->m_reinit = false;
cls->m_reinternalize_atoms = false;
continue;
}
SASSERT(cls->in_reinit_stack());
bool keep = false;
if (cls->reinternalize_atoms()) {
SASSERT(cls->get_num_atoms() == cls->get_num_literals());
for (unsigned j = 0; j < 2; j++) {
literal l = cls->get_literal(j);
if (l.var() < static_cast<int>(num_bool_vars)) {
// This boolean variable was not deleted during backtracking
//
// So, it is still a watch literal. I remove the watch, since
// the clause may have new watch-literals after reinitialization.
remove_watch_literal(cls, j);
}
}
unsigned num = cls->get_num_literals();
if (lit_occs_enabled()) {
for (unsigned j = 0; j < num; j++) {
literal l = cls->get_literal(j);
if (l.var() < static_cast<int>(num_bool_vars)) {
// This boolean variable was not deleted during backtracking
//
// So, remove it from lit_occs.
m_lit_occs[l.index()].erase(cls);
}
}
}
unsigned ilvl = 0;
for (unsigned j = 0; j < num; j++) {
expr * atom = cls->get_atom(j);
bool sign = cls->get_atom_sign(j);
// Atom can be (NOT foo). This can happen, for example, when
// the NOT-application is a child of an uninterpreted function symbol.
// So, when reinternalizing the NOT-atom I should set the gate_ctx to false,
// and force expression to be reinternalized.
// Otherwise I set gate_ctx to true
bool gate_ctx = !m_manager.is_not(atom);
internalize(atom, gate_ctx);
SASSERT(b_internalized(atom));
bool_var v = get_bool_var(atom);
DEBUG_CODE({
if (get_intern_level(v) > ilvl)
ilvl = get_intern_level(v);
});
literal l(v, sign);
cls->set_literal(j, l);
}
SASSERT(ilvl <= m_scope_lvl);
int w1_idx = select_watch_lit(cls, 0);
cls->swap_lits(0, w1_idx);
int w2_idx = select_watch_lit(cls, 1);
cls->swap_lits(1, w2_idx);
add_watch_literal(cls, 0);
add_watch_literal(cls, 1);
if (lit_occs_enabled())
add_lit_occs(cls);
literal l1 = cls->get_literal(0);
literal l2 = cls->get_literal(1);
if (get_assignment(l1) == l_false)
set_conflict(b_justification(cls));
else if (get_assignment(l2) == l_false)
assign(l1, b_justification(cls));
TRACE("reinit_clauses", tout << "reinit clause:\n"; display_clause_detail(tout, cls); tout << "\n";
tout << "activity: " << cls->get_activity() << ", num_bool_vars: " << num_bool_vars << ", scope_lvl: "
<< m_scope_lvl << "\n";);
keep = true;
}
else {
SASSERT(!cls->reinternalize_atoms());
literal l1 = cls->get_literal(0);
literal l2 = cls->get_literal(1);
if (get_assignment(l1) == l_false && is_empty_clause(cls)) {
set_conflict(b_justification(cls));
keep = true;
}
else if (get_assignment(l2) == l_false && get_assignment(l1) == l_undef && is_unit_clause(cls)) {
assign(l1, b_justification(cls));
keep = true;
}
}
// SASSERT(!(cls->get_num_literals() == 3 &&
// (cls->get_literal(0).index() == 624 || cls->get_literal(0).index() == 103 || cls->get_literal(0).index() == 629) &&
// (cls->get_literal(1).index() == 624 || cls->get_literal(1).index() == 103 || cls->get_literal(1).index() == 629) &&
// (cls->get_literal(2).index() == 624 || cls->get_literal(2).index() == 103 || cls->get_literal(2).index() == 629)));
if (keep && m_scope_lvl > m_base_lvl) {
m_clauses_to_reinit[m_scope_lvl].push_back(cls);
}
else {
// clause do not need to be in the reinit stack anymore,
// because it will be deleted when the base level is
// backtracked.
cls->release_atoms(m_manager);
cls->m_reinit = false;
cls->m_reinternalize_atoms = false;
}
}
v.reset();
}
CASSERT("reinit_clauses", check_clauses(m_lemmas));
CASSERT("reinit_clauses", check_lit_occs());
TRACE("reinit_clauses_bug", display_watch_lists(tout););
}
void context::reassert_units(unsigned units_to_reassert_lim) {
unsigned i = units_to_reassert_lim;
unsigned sz = m_units_to_reassert.size();
for (; i < sz; i++) {
expr * unit = m_units_to_reassert.get(i);
bool gate_ctx = true;
internalize(unit, gate_ctx);
bool_var v = get_bool_var(unit);
bool sign = m_units_to_reassert_sign[i] != 0;
literal l(v, sign);
assign(l, b_justification::mk_axiom());
TRACE("reassert_units", tout << "reasserting #" << unit->get_id() << " " << sign << " @ " << m_scope_lvl << "\n";);
}
if (at_base_level()) {
m_units_to_reassert.reset();
m_units_to_reassert_sign.reset();
}
}
/**
\brief Backtrack 'num_scopes' scope levels. Return the number
of boolean variables before reinitializing clauses. This value
is useful because it can be used to detect which boolean variables
were deleted.
\warning This method will not invoke reset_cache_generation.
*/
unsigned context::pop_scope_core(unsigned num_scopes) {
#ifndef SMTCOMP
if (m_fparams.m_trace_stream != NULL)
*m_fparams.m_trace_stream << "[pop] " << num_scopes << " " << m_scope_lvl << "\n";
#endif
TRACE("context", tout << "backtracking: " << num_scopes << "\n";);
TRACE("pop_scope_detail", display(tout););
SASSERT(num_scopes > 0);
SASSERT(num_scopes <= m_scope_lvl);
SASSERT(m_scopes.size() == m_scope_lvl);
unsigned new_lvl = m_scope_lvl - num_scopes;
cache_generation(new_lvl);
m_qmanager->pop(num_scopes);
m_case_split_queue->pop_scope(num_scopes);
TRACE("pop_scope", tout << "backtracking: " << num_scopes << ", new_lvl: " << new_lvl << "\n";);
scope & s = m_scopes[new_lvl];
TRACE("context", tout << "backtracking new_lvl: " << new_lvl << "\n";);
unsigned units_to_reassert_lim = s.m_units_to_reassert_lim;
if (new_lvl < m_base_lvl) {
base_scope & bs = m_base_scopes[new_lvl];
del_clauses(m_lemmas, bs.m_lemmas_lim);
m_simp_qhead = bs.m_simp_qhead_lim;
if (!bs.m_inconsistent) {
m_conflict = null_b_justification;
m_not_l = null_literal;
m_unsat_proof = 0;
}
m_base_scopes.shrink(new_lvl);
}
else {
m_conflict = null_b_justification;
m_not_l = null_literal;
}
del_clauses(m_aux_clauses, s.m_aux_clauses_lim);
m_relevancy_propagator->pop(num_scopes);
m_fingerprints.pop_scope(num_scopes);
unassign_vars(s.m_assigned_literals_lim);
undo_trail_stack(s.m_trail_stack_lim);
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it)
(*it)->pop_scope_eh(num_scopes);
del_justifications(m_justifications, s.m_justifications_lim);
m_asserted_formulas.pop_scope(num_scopes);
m_eq_propagation_queue.reset();
m_th_eq_propagation_queue.reset();
m_th_diseq_propagation_queue.reset();
m_atom_propagation_queue.reset();
m_region.pop_scope(num_scopes);
m_scopes.shrink(new_lvl);
m_scope_lvl = new_lvl;
if (new_lvl < m_base_lvl) {
m_base_lvl = new_lvl;
m_search_lvl = new_lvl; // Remark: not really necessary
}
unsigned num_bool_vars = get_num_bool_vars();
// any variable >= num_bool_vars was deleted during backtracking.
reinit_clauses(num_scopes, num_bool_vars);
reassert_units(units_to_reassert_lim);
TRACE("pop_scope_detail", tout << "end of pop_scope: \n"; display(tout););
CASSERT("context", check_invariant());
return num_bool_vars;
}
void context::pop_scope(unsigned num_scopes) {
pop_scope_core(num_scopes);
reset_cache_generation();
}
void context::pop_to_base_lvl() {
SASSERT(m_scope_lvl >= m_base_lvl);
if (!at_base_level()) {
unsigned num_lvls = m_scope_lvl - m_base_lvl;
pop_scope(num_lvls);
}
SASSERT(m_scope_lvl == m_base_lvl);
}
/**
\brief Simplify the given clause using the assignment. Return
true if the clause was already satisfied, and false otherwise.
\remark This method should only be invoked if we are at the
base level.
*/
bool context::simplify_clause(clause * cls) {
SASSERT(m_scope_lvl == m_base_lvl);
unsigned s = cls->get_num_literals();
if (get_assignment(cls->get_literal(0)) == l_true ||
get_assignment(cls->get_literal(1)) == l_true) {
// clause is already satisfied.
return true;
}
literal_buffer simp_lits;
unsigned i = 2;
unsigned j = i;
for(; i < s; i++) {
literal l = cls->get_literal(i);
switch(get_assignment(l)) {
case l_false:
if (m_manager.proofs_enabled())
simp_lits.push_back(~l);
if (lit_occs_enabled())
m_lit_occs[l.index()].erase(cls);
break;
case l_undef:
cls->set_literal(j, l);
j++;
break;
case l_true:
return true;
}
}
if (j < s) {
cls->set_num_literals(j);
SASSERT(j >= 2);
}
if (m_manager.proofs_enabled() && !simp_lits.empty()) {
SASSERT(m_scope_lvl == m_base_lvl);
justification * js = cls->get_justification();
justification * new_js = 0;
if (js->in_region())
new_js = mk_justification(unit_resolution_justification(m_region,
js,
simp_lits.size(),
simp_lits.c_ptr()));
else
new_js = alloc(unit_resolution_justification, js, simp_lits.size(), simp_lits.c_ptr());
cls->set_justification(new_js);
}
return false;
}
/**
\brief Simplify the given vector of clauses starting at the given position.
Return the number of deleted (already satisfied) clauses.
*/
unsigned context::simplify_clauses(clause_vector & clauses, unsigned starting_at) {
unsigned num_del_clauses = 0;
clause_vector::iterator it = clauses.begin();
clause_vector::iterator end = clauses.end();
it += starting_at;
clause_vector::iterator it2 = it;
for(; it != end; ++it) {
clause * cls = *it;
SASSERT(!cls->in_reinit_stack());
TRACE("simplify_clauses_bug", display_clause(tout, cls); tout << "\n";);
if (cls->deleted()) {
del_clause(cls);
num_del_clauses++;
}
else if (simplify_clause(cls)) {
for (unsigned idx = 0; idx < 2; idx++) {
literal l0 = cls->get_literal(idx);
b_justification l0_js = get_justification(l0.var());
if (l0_js != null_b_justification &&
l0_js.get_kind() == b_justification::CLAUSE &&
l0_js.get_clause() == cls) {
// cls is the explanation of l0
// it is safe to replace with axiom, we are at the base level.
SASSERT(m_scope_lvl == m_base_lvl);
bool_var v0 = l0.var();
if (m_manager.proofs_enabled()) {
SASSERT(m_search_lvl == m_base_lvl);
literal_buffer simp_lits;
unsigned num_lits = cls->get_num_literals();
for(unsigned i = 0; i < num_lits; i++) {
if (i != idx) {
literal l = cls->get_literal(i);
SASSERT(l != l0);
simp_lits.push_back(~l);
}
}
justification * cls_js = cls->get_justification();
justification * js = 0;
if (!cls_js || cls_js->in_region()) {
// If cls_js is 0 or is allocated in a region, then
// we can allocate the new justification in a region too.
js = mk_justification(unit_resolution_justification(m_region,
cls_js,
simp_lits.size(),
simp_lits.c_ptr()));
}
else {
js = alloc(unit_resolution_justification, cls_js, simp_lits.size(), simp_lits.c_ptr());
// js took ownership of the justification object.
cls->set_justification(0);
m_justifications.push_back(js);
}
m_bdata[v0].m_justification = b_justification(js);
}
else
m_bdata[v0].m_justification = b_justification::mk_axiom();
}
}
del_clause(cls);
num_del_clauses++;
}
else {
*it2 = *it;
++it2;
m_simp_counter += cls->get_num_literals();
}
}
clauses.set_end(it2);
CASSERT("simplify_clauses", check_invariant());
return num_del_clauses;
}
/**
\brief Simplify the set of clauses if possible (solver is at base level).
*/
void context::simplify_clauses() {
// Remark: when assumptions are used m_scope_lvl >= m_search_lvl > m_base_lvl. Therefore, no simplification is performed.
if (m_scope_lvl > m_base_lvl)
return;
unsigned sz = m_assigned_literals.size();
SASSERT(m_simp_qhead <= sz);
if (m_simp_qhead == sz || m_simp_counter > 0) {
TRACE("simplify_clauses", tout << "m_simp_qhead: " << m_simp_qhead << " m_simp_counter: " << m_simp_counter << "\n";);
return;
}
if (m_aux_clauses.empty() && m_lemmas.empty()) {
TRACE("simplify_clauses", tout << "no clauses to simplify\n";);
return;
}
TRACE("simplify_clauses_detail", tout << "before:\n"; display_clauses(tout, m_lemmas););
SASSERT(check_clauses(m_lemmas));
SASSERT(check_clauses(m_aux_clauses));
IF_VERBOSE(2, verbose_stream() << "simplifying clause set... "; verbose_stream().flush(););
// m_simp_counter is used to balance the cost of simplify_clause.
//
// After executing simplify_clauses, the counter will contain
// an approximation of the cost of executing simplify_clauses again.
// That is, the number of literals that will need to be visited.
//
// The value of the counter is decremented each time we visit
// a variable during propagation.
//
m_simp_counter = 0;
// the field m_simp_qhead is used to check whether there are
// new assigned literals at the base level.
m_simp_qhead = m_assigned_literals.size();
unsigned num_clauses = m_aux_clauses.size() + m_lemmas.size();
unsigned num_del_clauses = 0;
SASSERT(m_scope_lvl == m_base_lvl);
if (m_base_lvl == 0) {
num_del_clauses += simplify_clauses(m_aux_clauses, 0);
num_del_clauses += simplify_clauses(m_lemmas, 0);
}
else {
scope & s = m_scopes[m_base_lvl - 1];
base_scope & bs = m_base_scopes[m_base_lvl - 1];
num_del_clauses += simplify_clauses(m_aux_clauses, s.m_aux_clauses_lim);
num_del_clauses += simplify_clauses(m_lemmas, bs.m_lemmas_lim);
}
TRACE("simp_counter", tout << "simp_counter: " << m_simp_counter << " scope_lvl: " << m_scope_lvl << "\n";);
IF_VERBOSE(2, verbose_stream() << "num. deleted clauses: " << num_del_clauses << " (out of " << num_clauses << ")" << std::endl;);
TRACE("simplify_clauses_detail", tout << "after:\n"; display_clauses(tout, m_lemmas););
SASSERT(check_clauses(m_lemmas) && check_clauses(m_aux_clauses));
}
struct clause_lt {
bool operator()(clause * cls1, clause * cls2) const { return cls1->get_activity() > cls2->get_activity(); }
};
/**
\brief Delete low activity lemmas
*/
inline void context::del_inactive_lemmas() {
if (m_fparams.m_lemma_gc_half)
del_inactive_lemmas1();
else
del_inactive_lemmas2();
m_num_conflicts_since_lemma_gc = 0;
if (m_fparams.m_lemma_gc_strategy == LGC_GEOMETRIC)
m_lemma_gc_threshold = static_cast<unsigned>(m_lemma_gc_threshold * m_fparams.m_lemma_gc_factor);
}
/**
\brief Delete (approx.) half of low activity lemmas
*/
void context::del_inactive_lemmas1() {
unsigned sz = m_lemmas.size();
unsigned start_at = m_base_lvl == 0 ? 0 : m_base_scopes[m_base_lvl - 1].m_lemmas_lim;
SASSERT(start_at <= sz);
if (start_at + m_fparams.m_recent_lemmas_size >= sz)
return;
IF_VERBOSE(2, verbose_stream() << "deleting inactive lemmas... "; verbose_stream().flush(););
SASSERT (m_fparams.m_recent_lemmas_size < sz);
unsigned end_at = sz - m_fparams.m_recent_lemmas_size;
SASSERT(start_at < end_at);
std::stable_sort(m_lemmas.begin() + start_at, m_lemmas.begin() + end_at, clause_lt());
unsigned start_del_at = (start_at + end_at) / 2;
unsigned i = start_del_at;
unsigned j = i;
unsigned num_del_cls = 0;
TRACE("del_inactive_lemmas", tout << "sz: " << sz << ", start_at: " << start_at << ", end_at: " << end_at
<< ", start_del_at: " << start_del_at << "\n";);
for (; i < end_at; i++) {
clause * cls = m_lemmas[i];
if (can_delete(cls)) {
TRACE("del_inactive_lemmas", tout << "deleting: "; display_clause(tout, cls); tout << ", activity: " <<
cls->get_activity() << "\n";);
del_clause(cls);
num_del_cls++;
}
else {
m_lemmas[j] = cls;
j++;
}
}
// keep recent clauses
for (; i < sz; i++) {
clause * cls = m_lemmas[i];
if (cls->deleted() && can_delete(cls)) {
del_clause(cls);
num_del_cls++;
}
else {
m_lemmas[j] = cls;
j++;
}
}
m_lemmas.shrink(j);
if (m_fparams.m_clause_decay > 1) {
// rescale activity
for (i = start_at; i < j; i++) {
clause * cls = m_lemmas[i];
cls->set_activity(cls->get_activity() / m_fparams.m_clause_decay);
}
}
IF_VERBOSE(2, verbose_stream() << "num. deleted clauses: " << num_del_cls << " (out of " << sz << ")" << std::endl;);
}
/**
\brief More sophisticated version of del_inactive_lemmas. Here the lemmas are divided in two
groups (old and new) based on the value of m_new_old_ratio parameter.
A clause is deleted/retained based on its activity and relevancy. Clauses with several
unassigned literals are considered less relevant. The threshold used for activity and relevancy
depends on which group the clauses is in.
*/
void context::del_inactive_lemmas2() {
IF_VERBOSE(2, verbose_stream() << "deleting inactive clauses... "; verbose_stream().flush(););
unsigned sz = m_lemmas.size();
unsigned start_at = m_base_lvl == 0 ? 0 : m_base_scopes[m_base_lvl - 1].m_lemmas_lim;
SASSERT(start_at <= sz);
unsigned real_sz = sz - start_at;
// idx of the first learned clause considered "new"
unsigned new_first_idx = start_at + (real_sz / m_fparams.m_new_old_ratio) * (m_fparams.m_new_old_ratio - 1);
SASSERT(new_first_idx <= sz);
unsigned i = start_at;
unsigned j = i;
unsigned num_del_cls = 0;
for (; i < sz; i++) {
clause * cls = m_lemmas[i];
if (can_delete(cls)) {
if (cls->deleted()) {
// clause is already marked for deletion
del_clause(cls);
num_del_cls++;
continue;
}
// A clause is deleted if it has low activity and the number of unknowns is greater than a threshold.
// The activity threshold depends on how old the clause is.
unsigned act_threshold = m_fparams.m_old_clause_activity -
(m_fparams.m_old_clause_activity - m_fparams.m_new_clause_activity) * ((i - start_at) / real_sz);
if (cls->get_activity() < act_threshold) {
unsigned rel_threshold = (i >= new_first_idx ? m_fparams.m_new_clause_relevancy : m_fparams.m_old_clause_relevancy);
if (more_than_k_unassigned_literals(cls, rel_threshold)) {
del_clause(cls);
num_del_cls++;
continue;
}
}
}
m_lemmas[j] = cls;
j++;
cls->set_activity(static_cast<unsigned>(cls->get_activity() / m_fparams.m_inv_clause_decay));
}
SASSERT(j <= sz);
m_lemmas.shrink(j);
IF_VERBOSE(2, verbose_stream() << "num. deleted clauses: " << num_del_cls << " (out of " << sz << ")" << std::endl;);
}
/**
\brief Return true if "cls" has more than (or equal to) k unassigned literals.
*/
bool context::more_than_k_unassigned_literals(clause * cls, unsigned k) {
SASSERT(k > 0);
unsigned num_lits = cls->get_num_literals();
for(unsigned i = 0; i < num_lits; i++) {
literal l = cls->get_literal(i);
if (get_assignment(l) == l_undef) {
k--;
if (k == 0) {
return true;
}
}
}
return false;
}
void context::register_plugin(simplifier_plugin * s) {
SASSERT(!already_internalized());
SASSERT(m_scope_lvl == 0);
m_asserted_formulas.register_simplifier_plugin(s);
}
#ifdef Z3DEBUG
/**
\brief Return true if a symbol of the given theory was already internalized.
*/
bool context::already_internalized_theory(theory * th) const {
return already_internalized_theory_core(th, m_b_internalized_stack) || already_internalized_theory_core(th, m_e_internalized_stack);
}
/**
\brief Auxiliary method for #already_internalized_theory.
*/
bool context::already_internalized_theory_core(theory * th, expr_ref_vector const & s) const {
expr_mark visited;
family_id fid = th->get_id();
unsigned sz = s.size();
for (unsigned i = 0; i < sz; i++) {
expr * n = s.get(i);
if (uses_theory(n, fid, visited))
return true;
}
return false;
}
#endif
void context::register_plugin(theory * th) {
if (m_theories.get_plugin(th->get_family_id()) != 0)
return; // context already has a theory for the given family id.
SASSERT(std::find(m_theory_set.begin(), m_theory_set.end(), th) == m_theory_set.end());
SASSERT(!already_internalized_theory(th));
th->init(this);
m_theories.register_plugin(th);
m_theory_set.push_back(th);
{
#ifdef Z3DEBUG
// It is unsafe to invoke push_trail from the method push_scope_eh.
flet<bool> l(m_trail_enabled, false);
#endif
for (unsigned i = 0; i < m_scope_lvl; ++i)
th->push_scope_eh();
}
}
void context::push() {
TRACE("trigger_bug", tout << "context::push()\n";);
pop_to_base_lvl();
setup_context(false);
bool was_consistent = !inconsistent();
internalize_assertions(); // internalize assertions before invoking m_asserted_formulas.push_scope
propagate();
if (was_consistent && inconsistent()) {
// logical context became inconsistent during user PUSH
bool res = resolve_conflict(); // build the proof
SASSERT(!res);
}
push_scope();
m_base_scopes.push_back(base_scope());
base_scope & bs = m_base_scopes.back();
bs.m_lemmas_lim = m_lemmas.size();
bs.m_inconsistent = inconsistent();
bs.m_simp_qhead_lim = m_simp_qhead;
m_base_lvl++;
m_search_lvl++; // Not really necessary. But, it is useful to enforce the invariant m_search_lvl >= m_base_lvl
SASSERT(m_base_lvl <= m_scope_lvl);
}
void context::pop(unsigned num_scopes) {
SASSERT (num_scopes > 0);
pop_to_base_lvl();
pop_scope(num_scopes);
}
/**
\brief Free memory allocated by logical context.
*/
void context::flush() {
flet<bool> l1(m_flushing, true);
TRACE("flush", tout << "m_scope_lvl: " << m_scope_lvl << "\n";);
m_relevancy_propagator = 0;
m_model_generator->reset();
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it)
(*it)->flush_eh();
undo_trail_stack(0);
m_qmanager = 0;
del_clauses(m_aux_clauses, 0);
del_clauses(m_lemmas, 0);
del_justifications(m_justifications, 0);
if (m_is_diseq_tmp) {
m_is_diseq_tmp->del_eh(m_manager, false);
m_manager.dec_ref(m_is_diseq_tmp->get_owner());
enode::del_dummy(m_is_diseq_tmp);
m_is_diseq_tmp = 0;
}
std::for_each(m_almost_cg_tables.begin(), m_almost_cg_tables.end(), delete_proc<almost_cg_table>());
}
void context::assert_expr_core(expr * e, proof * pr) {
SASSERT(is_well_sorted(m_manager, e));
TRACE("begin_assert_expr", tout << mk_pp(e, m_manager) << "\n";);
TRACE("begin_assert_expr_ll", tout << mk_ll_pp(e, m_manager) << "\n";);
pop_to_base_lvl();
if (pr == 0)
m_asserted_formulas.assert_expr(e);
else
m_asserted_formulas.assert_expr(e, pr);
TRACE("end_assert_expr_ll", ast_mark m; m_asserted_formulas.display_ll(tout, m););
}
void context::assert_expr(expr * e) {
assert_expr(e, 0);
}
void context::assert_expr(expr * e, proof * pr) {
timeit tt(get_verbosity_level() >= 100, "simplifying");
assert_expr_core(e, pr);
}
bool context::reduce_assertions() {
if (!m_asserted_formulas.inconsistent()) {
SASSERT(at_base_level());
m_asserted_formulas.reduce();
}
return m_asserted_formulas.inconsistent();
}
void context::internalize_assertions() {
TRACE("internalize_assertions", tout << "internalize_assertions()...\n";);
timeit tt(get_verbosity_level() >= 100, "preprocessing");
reduce_assertions();
if (!m_asserted_formulas.inconsistent()) {
unsigned sz = m_asserted_formulas.get_num_formulas();
unsigned qhead = m_asserted_formulas.get_qhead();
while (qhead < sz) {
expr * f = m_asserted_formulas.get_formula(qhead);
proof * pr = m_asserted_formulas.get_formula_proof(qhead);
internalize_assertion(f, pr, 0);
qhead++;
}
m_asserted_formulas.commit();
}
if (m_asserted_formulas.inconsistent() && !inconsistent()) {
proof * pr = m_asserted_formulas.get_inconsistency_proof();
if (pr == 0) {
set_conflict(b_justification::mk_axiom());
}
else {
set_conflict(mk_justification(justification_proof_wrapper(*this, pr)));
m_unsat_proof = pr;
}
}
TRACE("internalize_assertions", tout << "after internalize_assertions()...\n";
tout << "inconsistent: " << inconsistent() << "\n";);
}
bool is_valid_assumption(ast_manager & m, expr * assumption) {
if (!m.is_bool(assumption))
return false;
if (is_uninterp_const(assumption))
return true;
if (m.is_not(assumption) && is_uninterp_const(to_app(assumption)->get_arg(0)))
return true;
return false;
}
/**
\brief Assumptions must be uninterpreted boolean constants (aka propositional variables).
*/
bool context::validate_assumptions(unsigned num_assumptions, expr * const * assumptions) {
for (unsigned i = 0; i < num_assumptions; i++) {
SASSERT(assumptions[i]);
if (!is_valid_assumption(m_manager, assumptions[i])) {
warning_msg("an assumption must be a propositional variable or the negation of one");
return false;
}
}
return true;
}
void context::init_assumptions(unsigned num_assumptions, expr * const * assumptions) {
reset_assumptions();
m_bool_var2assumption.reset();
m_unsat_core.reset();
if (num_assumptions > 0) {
propagate(); // we must give a chance to the theories to propagate before we create a new scope...
push_scope();
for (unsigned i = 0; i < num_assumptions; i++) {
expr * curr_assumption = assumptions[i];
SASSERT(is_valid_assumption(m_manager, curr_assumption));
proof * pr = m_manager.mk_asserted(curr_assumption);
internalize_assertion(curr_assumption, pr, 0);
literal l = get_literal(curr_assumption);
m_bool_var2assumption.insert(l.var(), curr_assumption);
// mark_as_relevant(l); <<< not needed
// internalize_assertion marked l as relevant.
SASSERT(is_relevant(l));
TRACE("assumptions", tout << mk_pp(curr_assumption, m_manager) << "\n";);
if (m_manager.proofs_enabled())
assign(l, mk_justification(justification_proof_wrapper(*this, pr)));
else
assign(l, b_justification::mk_axiom());
m_assumptions.push_back(l.var());
get_bdata(l.var()).m_assumption = true;
}
}
m_search_lvl = m_scope_lvl;
SASSERT(!(num_assumptions > 0) || m_search_lvl > m_base_lvl);
SASSERT(!(num_assumptions == 0) || m_search_lvl == m_base_lvl);
}
void context::reset_assumptions() {
bool_var_vector::iterator it = m_assumptions.begin();
bool_var_vector::iterator end = m_assumptions.end();
for (; it != end; ++it)
get_bdata(*it).m_assumption = false;
m_assumptions.reset();
}
void context::mk_unsat_core() {
SASSERT(inconsistent());
if (!tracking_assumptions()) {
SASSERT(m_assumptions.empty());
return;
}
obj_hashtable<expr> already_found_assumptions;
literal_vector::const_iterator it = m_conflict_resolution->begin_unsat_core();
literal_vector::const_iterator end = m_conflict_resolution->end_unsat_core();
for (; it != end; ++it) {
literal l = *it;
TRACE("unsat_core_bug", tout << "answer literal: " << l << "\n";);
SASSERT(get_bdata(l.var()).m_assumption);
SASSERT(m_bool_var2assumption.contains(l.var()));
expr * a = 0;
m_bool_var2assumption.find(l.var(), a);
SASSERT(a);
if (!already_found_assumptions.contains(a)) {
already_found_assumptions.insert(a);
m_unsat_core.push_back(a);
}
}
reset_assumptions();
pop_to_base_lvl(); // undo the push_scope() performed by init_assumptions
m_search_lvl = m_base_lvl;
std::sort(m_unsat_core.c_ptr(), m_unsat_core.c_ptr() + m_unsat_core.size(), ast_lt_proc());
TRACE("unsat_core_bug", tout << "unsat core:\n";
unsigned sz = m_unsat_core.size();
for (unsigned i = 0; i < sz; i++) {
tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n";
});
}
lbool context::get_implied_equalities(
unsigned num_terms,
expr* const* terms,
unsigned* class_ids,
unsigned num_assumptions,
expr * const * assumptions ) {
if (!validate_assumptions(num_assumptions, assumptions))
return l_undef;
simplifier& simp = get_simplifier();
expr_ref_vector sterms(m_manager), vals(m_manager), eqs(m_manager);
expr_ref vl(m_manager), eq(m_manager);
obj_map<expr, unsigned_vector > vals_map;
obj_map<expr, unsigned> visited_roots;
flet<bool> f2(m_fparams.m_model, true);
flet<bool> f3(m_fparams.m_array_canonize_simplify, true);
model_ref m;
proof_ref pr(m_manager);
union_find_default_ctx df;
union_find<union_find_default_ctx> uf(df);
lbool r;
m_last_search_failure = OK;
push();
for (unsigned i = 0; i < num_terms; ++i) {
expr_ref t(m_manager);
SASSERT(terms[i]->get_ref_count() > 0);
simp(terms[i], t, pr);
sterms.push_back(t);
if (!e_internalized(t)) {
internalize(t, false);
}
uf.mk_var();
}
lbool result = check(num_assumptions, assumptions);
//
// extract model in current satisfying state.
// The model contains all implied equalities, and
// possibly more depending on decisions.
//
if (result == l_false) {
pop(1);
m_last_search_failure = OK;
return result;
}
failure fail = m_last_search_failure;
get_model(m);
for (unsigned i = 0; i < num_terms; ++i) {
enode* e = get_enode(sterms[i].get());
expr* owner_root = e->get_root()->get_owner();
m->eval(owner_root, vl);
vals.push_back(vl);
}
//
// pop to base level to undo decision literals.
// The congruence closure contains only implied
// equalities, but possibly not them all.
// Force propagation by using push.
//
pop_to_base_lvl();
SASSERT(at_base_level());
push();
SASSERT(!inconsistent());
TRACE("get_implied_equalities", display(tout););
//
// if different enodes evaluate to the same value
// in current model,
// check if them being disequal is consistent.
// otherwise, add equality.
//
for (unsigned i = 0; i < num_terms; ++i) {
SASSERT(at_base_level());
enode* e = get_enode(sterms[i].get());
expr* owner_root = e->get_root()->get_owner();
unsigned idx_j;
vl = vals[i].get();
TRACE("get_implied_equalities",
tout << mk_ll_pp(sterms[i].get(), m_manager) << " |-> ";
tout << mk_ll_pp(owner_root, m_manager) << "|-> ";
tout << mk_ll_pp(vl, m_manager) << "\n";
);
if (visited_roots.find(owner_root, idx_j)) {
uf.merge(i, idx_j);
continue;
}
visited_roots.insert(owner_root, i);
unsigned_vector same_values;
if (!vals_map.find(vl, same_values)) {
same_values.push_back(i);
vals_map.insert(vl, same_values);
continue;
}
bool found_eq = false;
for (unsigned j = 0; !found_eq && j < same_values.size(); ++j) {
idx_j = same_values[j];
enode* other_node = get_enode(sterms[idx_j].get());
expr* other_root = other_node->get_root()->get_owner();
expr_ref neq(m_manager.mk_not(mk_eq_atom(owner_root, other_root)), m_manager);
push();
assert_expr(neq);
r = check(num_assumptions, assumptions);
pop(1);
TRACE("get_implied_equalities", tout << mk_pp(neq, m_manager) << " |-> " << r << "\n";);
if (r == l_false) {
uf.merge(i, idx_j);
found_eq = true;
}
SASSERT(!inconsistent());
}
if (!found_eq) {
same_values.push_back(i);
vals_map.insert(vl, same_values);
}
}
SASSERT(!inconsistent());
for (unsigned i = 0; i < num_terms; ++i) {
unsigned j = uf.find(i);
enode* e = get_enode(sterms[j].get());
class_ids[i] = e->get_root()->get_owner_id();
}
TRACE("get_implied_equalities",
for (unsigned i = 0; i < num_terms; ++i) {
tout << mk_pp(sterms[i].get(), m_manager) << " |-> #" << class_ids[i] << "\n";
}
display(tout););
pop(2);
TRACE("get_implied_equalities", display(tout); );
/*
DEBUG_CODE(
for (unsigned i = 0; result != l_undef && i < num_terms; ++i) {
for (unsigned j = i+1; j < num_terms; ++j) {
if (class_ids[i] != class_ids[j]) {
expr* e1 = sterms[i].get();
expr* e2 = sterms[j].get();
if (m_manager.get_sort(e1) != m_manager.get_sort(e2)) {
continue;
}
SASSERT(!inconsistent());
expr_ref neq(m_manager.mk_not(mk_eq_atom(e1,e2)), m_manager);
push();
assert_expr(neq);
r = check(num_assumptions, assumptions);
pop(1);
if (r == l_false) {
enable_trace("array");
enable_trace("arith");
enable_trace("context");
enable_trace("unsat_core_bug");
enable_trace("after_search");
enable_trace("display_unsat_core");
SASSERT(!inconsistent());
{
flet<bool> f3(m_fparams.m_smtlib_dump_assertions, true);
unsigned sz = m_asserted_formulas.get_num_formulas();
for (unsigned l = 0; l < sz; ++l) {
expr * f = m_asserted_formulas.get_formula(l);
save_assertion_for_pp(f);
}
save_assertion_for_pp(neq);
display_assertions_as_smt_problem();
}
push();
assert_expr(neq);
r = check(num_assumptions, assumptions);
pop(1);
pop_to_base_lvl();
push();
IF_VERBOSE(0,
display(verbose_stream());
for (unsigned k = 0; k < num_terms; ++k) {
verbose_stream() << "id: " << class_ids[k] << "\n"
<< "term: " << mk_pp(sterms[k].get(), m_manager) << "\n"
<< "val: " << mk_pp(vals[k].get(), m_manager) << "\n";
}
verbose_stream() << "\n";
verbose_stream() << "term1: " << mk_pp(e1, m_manager) << "\n";
verbose_stream() << "term2: " << mk_pp(e2, m_manager) << "\n";
verbose_stream() << "val1: " << mk_pp(vals[i].get(), m_manager) << "\n";
verbose_stream() << "val2: " << mk_pp(vals[j].get(), m_manager) << "\n";);
SASSERT(r != l_false);
exit(0);
}
SASSERT(r != l_false);
}
}
}
);
*/
m_last_search_failure = fail;
return result;
}
/**
\brief Make some checks before starting the search.
Return true if succeeded.
*/
bool context::check_preamble(bool reset_cancel) {
#ifndef SMTCOMP
if (m_fparams.m_trace_stream != NULL)
*m_fparams.m_trace_stream << "[begin-check] " << m_scope_lvl << "\n";
#endif
if (reset_cancel) {
m_cancel_flag = false;
m_asserted_formulas.set_cancel_flag(false);
}
if (memory::above_high_watermark()) {
m_last_search_failure = MEMOUT;
return false;
}
return true;
}
/**
\brief Execute some finalization code after performing the search.
*/
void context::check_finalize(lbool r) {
TRACE("after_search", display(tout););
display_profile(verbose_stream());
}
/**
\brief Setup the logical context based on the current set of
asserted formulas and execute the check command.
\remark A logical context can only be configured at scope level 0,
and before internalizing any formulas.
*/
lbool context::setup_and_check(bool reset_cancel) {
if (!check_preamble(reset_cancel))
return l_undef;
SASSERT(m_scope_lvl == 0);
SASSERT(!m_setup.already_configured());
setup_context(m_fparams.m_auto_config);
internalize_assertions();
lbool r = l_undef;
if (m_asserted_formulas.inconsistent()) {
r = l_false;
}
else {
TRACE("after_internalization", display(tout););
if (inconsistent()) {
bool res = resolve_conflict(); // build the proof
SASSERT(!res);
r = l_false;
}
else {
r = search();
}
}
check_finalize(r);
return r;
}
config_mode context::get_config_mode(bool use_static_features) const {
if (!m_fparams.m_auto_config)
return CFG_BASIC;
if (use_static_features)
return CFG_AUTO;
return CFG_LOGIC;
}
void context::setup_context(bool use_static_features) {
if (m_setup.already_configured())
return;
m_setup(get_config_mode(use_static_features));
setup_components();
#ifndef _EXTERNAL_RELEASE
if (m_fparams.m_display_installed_theories) {
std::cout << "(theories";
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it) {
std::cout << " " << (*it)->get_name();
}
std::cout << ")" << std::endl;
}
#endif
}
void context::setup_components() {
m_asserted_formulas.setup();
m_random.set_seed(m_fparams.m_random_seed);
m_dyn_ack_manager.setup();
m_conflict_resolution->setup();
if (!relevancy())
m_fparams.m_relevancy_lemma = false;
// setup all the theories
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it)
(*it)->setup();
}
lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel) {
m_stats.m_num_checks++;
TRACE("check_bug", tout << "STARTING check(num_assumptions, assumptions)\n";
tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n";
m_asserted_formulas.display(tout);
tout << "-----------------------\n";
display(tout););
if (!m_unsat_core.empty())
m_unsat_core.reset();
if (!check_preamble(reset_cancel))
return l_undef;
if (!validate_assumptions(num_assumptions, assumptions))
return l_undef;
TRACE("check_bug", tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n";);
TRACE("unsat_core_bug", for (unsigned i = 0; i < num_assumptions; i++) { tout << mk_pp(assumptions[i], m_manager) << "\n";});
pop_to_base_lvl();
TRACE("before_search", display(tout););
SASSERT(at_base_level());
lbool r = l_undef;
if (inconsistent()) {
r = l_false;
}
else {
setup_context(false);
internalize_assertions();
TRACE("after_internalize_assertions", display(tout););
if (m_asserted_formulas.inconsistent()) {
r = l_false;
}
else {
init_assumptions(num_assumptions, assumptions);
TRACE("after_internalization", display(tout););
if (inconsistent()) {
bool res = resolve_conflict(); // build the proof
SASSERT(!res);
mk_unsat_core();
r = l_false;
}
else {
r = search();
if (r == l_false)
mk_unsat_core();
}
}
}
check_finalize(r);
return r;
}
void context::init_search() {
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it)
(*it)->init_search_eh();
m_qmanager->init_search_eh();
m_assumption_core.reset();
m_incomplete_theories.reset();
m_num_conflicts = 0;
m_num_conflicts_since_restart = 0;
m_num_conflicts_since_lemma_gc = 0;
m_restart_threshold = m_fparams.m_restart_initial;
m_restart_outer_threshold = m_fparams.m_restart_initial;
m_agility = 0.0;
m_luby_idx = 1;
m_lemma_gc_threshold = m_fparams.m_lemma_gc_initial;
m_last_search_failure = OK;
m_unsat_proof = 0;
m_unsat_core .reset();
m_dyn_ack_manager .init_search_eh();
m_final_check_idx = 0;
m_phase_default = false;
m_case_split_queue ->init_search_eh();
m_next_progress_sample = 0;
TRACE("literal_occ", display_literal_num_occs(tout););
m_timer.start();
m_instr.start();
}
void context::end_search() {
m_case_split_queue ->end_search_eh();
}
void context::inc_limits() {
if (m_num_conflicts_since_restart >= m_restart_threshold) {
switch (m_fparams.m_restart_strategy) {
case RS_GEOMETRIC:
m_restart_threshold = static_cast<unsigned>(m_restart_threshold * m_fparams.m_restart_factor);
break;
case RS_IN_OUT_GEOMETRIC:
m_restart_threshold = static_cast<unsigned>(m_restart_threshold * m_fparams.m_restart_factor);
if (m_restart_threshold > m_restart_outer_threshold) {
m_restart_threshold = m_fparams.m_restart_initial;
m_restart_outer_threshold = static_cast<unsigned>(m_restart_outer_threshold * m_fparams.m_restart_factor);
}
break;
case RS_LUBY:
m_luby_idx ++;
m_restart_threshold = static_cast<unsigned>(get_luby(m_luby_idx) * m_fparams.m_restart_initial);
break;
case RS_FIXED:
break;
case RS_ARITHMETIC:
m_restart_threshold = static_cast<unsigned>(m_restart_threshold + m_fparams.m_restart_factor);
break;
default:
break;
}
}
m_num_conflicts_since_restart = 0;
}
struct context::scoped_mk_model {
context & m_ctx;
scoped_mk_model(context & ctx):m_ctx(ctx) {
m_ctx.m_proto_model = 0;
m_ctx.m_model = 0;
}
~scoped_mk_model() {
if (m_ctx.m_proto_model.get() != 0) {
m_ctx.m_model = m_ctx.m_proto_model->mk_model();
m_ctx.m_proto_model = 0; // proto_model is not needed anymore.
}
}
};
lbool context::search() {
#ifndef _EXTERNAL_RELEASE
if (m_fparams.m_abort_after_preproc) {
exit(1);
}
#endif
timeit tt(get_verbosity_level() >= 100, "searching");
scoped_mk_model smk(*this);
SASSERT(at_search_level());
TRACE("search", display(tout); display_enodes_lbls(tout););
TRACE("search_detail", m_asserted_formulas.display(tout););
init_search();
flet<bool> l(m_searching, true);
TRACE("after_init_search", display(tout););
IF_VERBOSE(2, verbose_stream() << "searching...\n";);
TRACE("search_lite", tout << "searching...\n";);
lbool status = l_undef;
unsigned curr_lvl = m_scope_lvl;
while (true) {
SASSERT(!inconsistent());
status = bounded_search();
TRACE("search_bug", tout << "status: " << status << ", inconsistent: " << inconsistent() << "\n";);
TRACE("assigned_literals_per_lvl", display_num_assigned_literals_per_lvl(tout);
tout << ", num_assigned: " << m_assigned_literals.size() << "\n";);
if (m_last_search_failure != OK) {
if (status != l_false) {
// build candidate model before returning
mk_proto_model(status);
}
break;
}
bool force_restart = false;
if (status == l_false) {
break;
}
else if (status == l_true) {
SASSERT(!inconsistent());
mk_proto_model(l_true);
// possible outcomes DONE l_true, DONE l_undef, CONTINUE
quantifier_manager::check_model_result cmr = m_qmanager->check_model(m_proto_model.get(), m_model_generator->get_root2value());
if (cmr == quantifier_manager::SAT) {
// done
break;
}
if (cmr == quantifier_manager::UNKNOWN) {
// giving up
m_last_search_failure = QUANTIFIERS;
status = l_undef;
break;
}
status = l_undef;
force_restart = true;
}
SASSERT(status == l_undef);
inc_limits();
if (force_restart || !m_fparams.m_restart_adaptive || m_agility < m_fparams.m_restart_agility_threshold) {
SASSERT(!inconsistent());
IF_VERBOSE(1, verbose_stream() << "restarting... propagations: " << m_stats.m_num_propagations
<< ", decisions: " << m_stats.m_num_decisions
<< ", conflicts: " << m_stats.m_num_conflicts << ", restart: " << m_restart_threshold;
if (m_fparams.m_restart_strategy == RS_IN_OUT_GEOMETRIC) {
verbose_stream() << ", restart-outer: " << m_restart_outer_threshold;
}
if (m_fparams.m_restart_adaptive) {
verbose_stream() << ", agility: " << m_agility;
}
verbose_stream() << std::endl; verbose_stream().flush(););
// execute the restart
m_stats.m_num_restarts++;
if (m_scope_lvl > curr_lvl) {
pop_scope(m_scope_lvl - curr_lvl);
SASSERT(at_search_level());
}
ptr_vector<theory>::iterator it = m_theory_set.begin();
ptr_vector<theory>::iterator end = m_theory_set.end();
for (; it != end; ++it)
(*it)->restart_eh();
TRACE("mbqi_bug_detail", tout << "before instantiating quantifiers...\n";);
m_qmanager->restart_eh();
}
if (m_fparams.m_simplify_clauses)
simplify_clauses();
if (m_fparams.m_lemma_gc_strategy == LGC_AT_RESTART)
del_inactive_lemmas();
}
TRACE("search_lite", tout << "status: " << status << "\n";);
TRACE("guessed_literals",
expr_ref_vector guessed_lits(m_manager);
get_guessed_literals(guessed_lits);
unsigned sz = guessed_lits.size();
for (unsigned i = 0; i < sz; i++) {
tout << mk_pp(guessed_lits.get(i), m_manager) << "\n";
});
end_search();
return status;
}
void context::tick(unsigned & counter) const {
counter++;
if (counter > m_fparams.m_tick) {
IF_VERBOSE(3, verbose_stream() << "working...";
verbose_stream() << " num. conflicts: " << m_num_conflicts;
// verbose_stream() << " lemma avg. activity: " << get_lemma_avg_activity();
if (m_fparams.m_restart_adaptive)
verbose_stream() << " agility: " << m_agility;
verbose_stream() << std::endl; verbose_stream().flush(););
TRACE("assigned_literals_per_lvl", display_num_assigned_literals_per_lvl(tout); tout << "\n";);
counter = 0;
}
}
lbool context::bounded_search() {
SASSERT(!inconsistent());
unsigned counter = 0;
TRACE("bounded_search", tout << "starting bounded search...\n";);
while (true) {
while (!propagate()) {
TRACE_CODE({
static bool first_propagate = true;
if (first_propagate) {
first_propagate = false;
TRACE("after_first_propagate", display(tout););
}
});
tick(counter);
if (!resolve_conflict())
return l_false;
SASSERT(m_scope_lvl >= m_base_lvl);
if (!inconsistent()) {
if (resource_limits_exceeded())
return l_undef;
if (m_num_conflicts_since_restart > m_restart_threshold && m_scope_lvl - m_base_lvl > 2) {
TRACE("search_bug", tout << "bounded-search return undef, inconsistent: " << inconsistent() << "\n";);
return l_undef; // restart
}
if (m_num_conflicts > m_fparams.m_max_conflicts) {
TRACE("search_bug", tout << "bounded-search return undef, inconsistent: " << inconsistent() << "\n";);
m_last_search_failure = NUM_CONFLICTS;
return l_undef;
}
}
if (m_num_conflicts_since_lemma_gc > m_lemma_gc_threshold &&
(m_fparams.m_lemma_gc_strategy == LGC_FIXED || m_fparams.m_lemma_gc_strategy == LGC_GEOMETRIC)) {
del_inactive_lemmas();
}
m_dyn_ack_manager.propagate_eh();
CASSERT("dyn_ack", check_clauses(m_lemmas) && check_clauses(m_aux_clauses));
}
if (resource_limits_exceeded()) {
SASSERT(!inconsistent());
return l_undef;
}
if (m_base_lvl == m_scope_lvl && m_fparams.m_simplify_clauses)
simplify_clauses();
if (!decide()) {
final_check_status fcs = final_check();
TRACE("final_check_result", tout << "fcs: " << fcs << " last_search_failure: " << m_last_search_failure << "\n";);
switch (fcs) {
case FC_DONE:
return l_true;
case FC_CONTINUE:
break;
case FC_GIVEUP:
return l_undef;
}
}
if (resource_limits_exceeded()) {
SASSERT(!inconsistent());
return l_undef;
}
}
}
bool context::resource_limits_exceeded() {
if (m_searching) {
// Some of the flags only make sense to check when searching.
// For example, the timer is only started in init_search().
if (m_last_search_failure != OK)
return true;
if (m_timer.ms_timeout(m_fparams.m_soft_timeout)) {
m_last_search_failure = TIMEOUT;
return true;
}
if (m_instr.is_instruction_maxed(m_fparams.m_instr_out)) {
m_last_search_failure = TIMEOUT;
return true;
}
if (m_progress_callback) {
m_progress_callback->fast_progress_sample();
if (m_fparams.m_progress_sampling_freq > 0 && m_timer.ms_timeout(m_next_progress_sample + 1)) {
m_progress_callback->slow_progress_sample();
m_next_progress_sample = (unsigned)(m_timer.get_seconds() * 1000) + m_fparams.m_progress_sampling_freq;
}
}
}
if (m_cancel_flag) {
m_last_search_failure = CANCELED;
return true;
}
if (memory::above_high_watermark()) {
m_last_search_failure = MEMOUT;
return true;
}
return false;
}
final_check_status context::final_check() {
TRACE("final_check", tout << "final_check inconsistent: " << inconsistent() << "\n"; display(tout); display_normalized_enodes(tout););
CASSERT("relevancy", check_relevancy());
if (m_fparams.m_model_on_final_check) {
mk_proto_model(l_undef);
model_pp(std::cout, *m_proto_model);
std::cout << "END_OF_MODEL\n";
std::cout.flush();
}
m_stats.m_num_final_checks++;
final_check_status ok = m_qmanager->final_check_eh(false);
if (ok != FC_DONE)
return ok;
m_incomplete_theories.reset();
unsigned old_idx = m_final_check_idx;
unsigned num_th = m_theory_set.size();
unsigned range = num_th + 1;
final_check_status result = FC_DONE;
failure f = OK;
do {
TRACE("final_check_step", tout << "processing: " << m_final_check_idx << ", result: " << result << "\n";);
final_check_status ok;
if (m_final_check_idx < num_th) {
theory * th = m_theory_set[m_final_check_idx];
IF_VERBOSE(100, verbose_stream() << "final check '" << th->get_name() << "' ...\n";);
ok = th->final_check_eh();
TRACE("final_check_step", tout << "final check '" << th->get_name() << " ok: " << ok << " inconsistent " << inconsistent() << "\n";);
if (ok == FC_GIVEUP) {
f = THEORY;
m_incomplete_theories.push_back(th);
}
}
else {
ok = m_qmanager->final_check_eh(true);
TRACE("final_check_step", tout << "quantifier ok: " << ok << " " << "inconsistent " << inconsistent() << "\n";);
}
m_final_check_idx = (m_final_check_idx + 1) % range;
// IF_VERBOSE(1000, verbose_stream() << "final check status: " << ok << "\n";);
switch (ok) {
case FC_DONE:
break;
case FC_GIVEUP:
result = FC_GIVEUP;
break;
case FC_CONTINUE:
return FC_CONTINUE;
break;
}
}
while (m_final_check_idx != old_idx);
TRACE("final_check_step", tout << "result: " << result << "\n";);
if (can_propagate()) {
TRACE("final_check_step", tout << "can propagate: continue...\n";);
return FC_CONTINUE;
}
SASSERT(result != FC_DONE || check_th_diseq_propagation());
TRACE("final_check_step", tout << "RESULT final_check: " << result << "\n";);
if (result == FC_GIVEUP && f != OK)
m_last_search_failure = f;
return result;
}
void context::check_proof(proof * pr) {
if (m_manager.proofs_enabled() && m_fparams.m_check_proof) {
proof_checker pf(m_manager);
expr_ref_vector side_conditions(m_manager);
pf.check(pr, side_conditions);
}
}
void context::forget_phase_of_vars_in_current_level() {
unsigned head = m_scope_lvl == 0 ? 0 : m_scopes[m_scope_lvl - 1].m_assigned_literals_lim;
unsigned sz = m_assigned_literals.size();
for (unsigned i = head; i < sz; i++) {
literal l = m_assigned_literals[i];
bool_var v = l.var();
TRACE("forget_phase", tout << "forgeting phase of l: " << l << "\n";);
m_bdata[v].m_phase_available = false;
}
}
bool context::resolve_conflict() {
m_stats.m_num_conflicts++;
m_num_conflicts ++;
m_num_conflicts_since_restart ++;
m_num_conflicts_since_lemma_gc ++;
switch (m_conflict.get_kind()) {
case b_justification::CLAUSE:
case b_justification::BIN_CLAUSE:
m_stats.m_num_sat_conflicts++;
break;
default:
break;
}
if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE || m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2)
forget_phase_of_vars_in_current_level();
m_atom_propagation_queue.reset();
m_eq_propagation_queue.reset();
m_th_eq_propagation_queue.reset();
m_th_diseq_propagation_queue.reset();
if (m_conflict_resolution->resolve(m_conflict, m_not_l)) {
unsigned new_lvl = m_conflict_resolution->get_new_scope_lvl();
unsigned num_lits = m_conflict_resolution->get_lemma_num_literals();
literal * lits = m_conflict_resolution->get_lemma_literals();
SASSERT(num_lits > 0);
unsigned conflict_lvl = get_assign_level(lits[0]);
SASSERT(conflict_lvl <= m_scope_lvl);
// When num_lits == 1, then the default behavior is to go
// to base-level. If the problem has quantifiers, it may be
// too expensive to do that, since all instances will need to
// be recreated. If that is the case, I store the assertions in
// a special vector and keep reasserting whenever I backtrack.
// Moreover, I backtrack only one level.
bool delay_forced_restart =
m_fparams.m_delay_units &&
internalized_quantifiers() &&
num_lits == 1 &&
conflict_lvl > m_search_lvl + 1 &&
!m_manager.proofs_enabled() &&
m_units_to_reassert.size() < m_fparams.m_delay_units_threshold;
if (delay_forced_restart) {
new_lvl = conflict_lvl - 1;
}
// Some of the literals/enodes of the conflict clause will be destroyed during
// backtracking, and will need to be recreated. However, I want to keep
// the generation number for enodes that are going to be recreated. See
// comment in cache_generation(unsigned).
if (m_conflict_resolution->get_lemma_intern_lvl() > new_lvl)
cache_generation(num_lits, lits, new_lvl);
SASSERT(new_lvl < m_scope_lvl);
TRACE("resolve_conflict_bug",
tout << "m_scope_lvl: " << m_scope_lvl << ", new_lvl: " << new_lvl << ", lemma_intern_lvl: " << m_conflict_resolution->get_lemma_intern_lvl() << "\n";
tout << "num_lits: " << num_lits << "\n";
for (unsigned i = 0; i < num_lits; i++) {
literal l = lits[i];
tout << l << " ";
display_literal(tout, l);
tout << ", ilvl: " << get_intern_level(l.var()) << "\n"
<< mk_pp(bool_var2expr(l.var()), m_manager) << "\n";
});
#ifndef SMTCOMP
if (m_fparams.m_trace_stream != NULL) {
*m_fparams.m_trace_stream << "[conflict] ";
display_literals(*m_fparams.m_trace_stream, num_lits, lits);
*m_fparams.m_trace_stream << "\n";
}
#endif
#ifdef Z3DEBUG
expr_ref_vector expr_lits(m_manager);
svector<bool> expr_signs;
for (unsigned i = 0; i < num_lits; i++) {
literal l = lits[i];
SASSERT(get_assignment(l) == l_false);
expr_lits.push_back(bool_var2expr(l.var()));
expr_signs.push_back(l.sign());
}
#endif
proof * pr = 0;
if (m_manager.proofs_enabled()) {
pr = m_conflict_resolution->get_lemma_proof();
// check_proof(pr);
TRACE("context_proof", tout << mk_ll_pp(pr, m_manager););
TRACE("context_proof_hack",
static ast_mark visited;
ast_ll_pp(tout, m_manager, pr, visited););
}
// I invoke pop_scope_core instead of pop_scope because I don't want
// to reset cached generations... I need them to rebuild the literals
// of the new conflict clause.
unsigned num_bool_vars = pop_scope_core(m_scope_lvl - new_lvl);
SASSERT(m_scope_lvl == new_lvl);
// the logical context may still be in conflict after
// clauses are reinitialized in pop_scope.
if (m_conflict_resolution->get_lemma_intern_lvl() > m_scope_lvl) {
expr * * atoms = m_conflict_resolution->get_lemma_atoms();
for (unsigned i = 0; i < num_lits; i++) {
literal l = lits[i];
if (l.var() >= static_cast<int>(num_bool_vars)) {
// This boolean variable was deleted during backtracking, it need to be recreated.
// Remark: atom may be a negative literal (not a). Z3 creates Boolean variables for not-gates that
// are nested in terms. Example: let f be a uninterpreted function from Bool -> Int.
// Then, given the term (f (not a)), Z3 will create a boolean variable for (not a) when internalizing (f (not a)).
expr * atom = atoms[i];
internalize(atom, true);
// If atom is actually a negative literal (not a), then get_bool_var will return return null_bool_var.
// Thus, we must use get_literal instead. This was a bug/crash in Z3 <= 4.0
literal new_l = get_literal(atom);
if (l.sign())
new_l.neg();
// For reference, here is the buggy version
// BEGIN BUGGY VERSION
// bool_var v = get_bool_var(atom);
// CTRACE("resolve_conflict_crash", v == null_bool_var, tout << mk_ismt2_pp(atom, m_manager) << "\n";);
// SASSERT(v != null_bool_var);
// literal new_l = literal(v, l.sign());
// END BUGGY VERSION
lits[i] = new_l;
}
}
}
// Resetting the cache manually because I did not invoke pop_scope, but pop_scope_core
reset_cache_generation();
TRACE("resolve_conflict_bug",
tout << "AFTER m_scope_lvl: " << m_scope_lvl << ", new_lvl: " << new_lvl << ", lemma_intern_lvl: " <<
m_conflict_resolution->get_lemma_intern_lvl() << "\n";
tout << "num_lits: " << num_lits << "\n";
for (unsigned i = 0; i < num_lits; i++) {
literal l = lits[i];
tout << l << " ";
display_literal(tout, l);
tout << ", ilvl: " << get_intern_level(l.var()) << "\n"
<< mk_pp(bool_var2expr(l.var()), m_manager) << "\n";
});
#ifdef Z3DEBUG
for (unsigned i = 0; i < num_lits; i++) {
literal l = lits[i];
if (m_manager.is_not(expr_lits.get(i))) {
// the sign must have flipped when internalizing
expr * real_atom = to_app(expr_lits.get(i))->get_arg(0);
SASSERT(real_atom == bool_var2expr(l.var()));
SASSERT(expr_signs[i] != l.sign());
}
else {
SASSERT(expr_lits.get(i) == bool_var2expr(l.var()));
SASSERT(expr_signs[i] == l.sign());
}
}
#endif
justification * js = 0;
if (m_manager.proofs_enabled()) {
js = alloc(justification_proof_wrapper, *this, pr, false);
}
#if 0
{
static unsigned counter = 0;
static uint64 total = 0;
static unsigned max = 0;
counter++;
total += num_lits;
if (num_lits > max) {
max = num_lits;
}
if (counter % 1000 == 0) {
verbose_stream() << "[sat] avg. clause size: " << ((double) total/(double) counter) << ", max: " << max << std::endl;
for (unsigned i = 0; i < num_lits; i++) {
literal l = lits[i];
verbose_stream() << l.sign() << " " << mk_pp(bool_var2expr(l.var()), m_manager) << "\n";
}
}
}
#endif
mk_clause(num_lits, lits, js, CLS_LEARNED);
if (delay_forced_restart) {
SASSERT(num_lits == 1);
expr * unit = bool_var2expr(lits[0].var());
bool unit_sign = lits[0].sign();
m_units_to_reassert.push_back(unit);
m_units_to_reassert_sign.push_back(unit_sign);
TRACE("reassert_units", tout << "asserting #" << unit->get_id() << " " << unit_sign << " @ " << m_scope_lvl << "\n";);
}
m_conflict_resolution->release_lemma_atoms();
TRACE("context_lemma", tout << "new lemma: ";
literal_vector v(num_lits, lits);
std::sort(v.begin(), v.end());
for (unsigned i = 0; i < num_lits; i++) {
display_literal(tout, v[i]);
tout << "\n";
v[i].display(tout, m_manager, m_bool_var2expr.c_ptr());
tout << "\n\n";
}
tout << "\n";);
decay_bvar_activity();
update_phase_cache_counter();
return true;
}
else if (m_manager.proofs_enabled()) {
m_unsat_proof = m_conflict_resolution->get_lemma_proof();
check_proof(m_unsat_proof);
}
return false;
}
void context::get_relevant_labels(expr* cnstr, buffer<symbol> & result) {
if (m_fparams.m_check_at_labels) {
check_at_labels checker(m_manager);
if (cnstr && !checker.check(cnstr)) {
warning_msg("Boogie generated formula that can require multiple '@' labels in a counter-example");
}
else {
unsigned nf = m_asserted_formulas.get_num_formulas();
for (unsigned i = 0; i < nf; ++i) {
expr* fml = m_asserted_formulas.get_formula(i);
if (!checker.check(fml)) {
warning_msg("Boogie generated formula that can require multiple '@' labels in a counter-example");
break;
}
}
}
}
SASSERT(!inconsistent());
unsigned sz = m_b_internalized_stack.size();
for (unsigned i = 0; i < sz; i++) {
expr * curr = m_b_internalized_stack.get(i);
if (is_relevant(curr) && get_assignment(curr) == l_true) {
// if curr is a label literal, then its tags will be copied to result.
m_manager.is_label_lit(curr, result);
}
}
}
/**
\brief Collect relevant literals that may be used to block the current assignment.
If at_lbls is true, then only labels that contains '@' are considered. (This is a hack for Boogie).
This hack is also available in the Simplify theorem prover.
*/
void context::get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) {
SASSERT(!inconsistent());
buffer<symbol> lbls;
unsigned sz = m_b_internalized_stack.size();
for (unsigned i = 0; i < sz; i++) {
expr * curr = m_b_internalized_stack.get(i);
if (is_relevant(curr) && get_assignment(curr) == l_true) {
lbls.reset();
if (m_manager.is_label_lit(curr, lbls)) {
bool include = false;
if (at_lbls) {
// include if there is a label with the '@' sign.
buffer<symbol>::const_iterator it = lbls.begin();
buffer<symbol>::const_iterator end = lbls.end();
for (; it != end; ++it) {
symbol const & s = *it;
if (s.contains('@')) {
include = true;
break;
}
}
}
else {
include = true;
}
if (include)
result.push_back(curr);
}
}
}
}
/**
\brief Store in result the (relevant) literal assigned by the
logical context.
*/
void context::get_relevant_literals(expr_ref_vector & result) {
SASSERT(!inconsistent());
unsigned sz = m_b_internalized_stack.size();
for (unsigned i = 0; i < sz; i++) {
expr * curr = m_b_internalized_stack.get(i);
if (is_relevant(curr)) {
switch (get_assignment(curr)) {
case l_true:
result.push_back(curr);
break;
case l_false:
result.push_back(m_manager.mk_not(curr));
break;
default:
break;
}
}
}
}
/**
\brief Store the current set of guessed literals (i.e., case splits).
*/
void context::get_guessed_literals(expr_ref_vector & result) {
// The literals between [m_base_lvl, m_search_lvl) are not guesses but assumptions.
SASSERT(m_base_lvl <= m_scopes.size());
if (m_search_lvl == m_scopes.size()) {
// do nothing... there are guesses...
}
for (unsigned i = m_search_lvl; i < m_scope_lvl; i++) {
// This method assumes the first literal assigned in a non base scope level is a guess.
scope & s = m_scopes[i];
unsigned guess_idx = s.m_assigned_literals_lim;
literal guess = m_assigned_literals[guess_idx];
SASSERT(get_justification(guess.var()).get_kind() == b_justification::AXIOM);
expr_ref lit(m_manager);
literal2expr(guess, lit);
result.push_back(lit);
}
}
/**
\brief Undo object for bool var m_true_first field update.
*/
class set_true_first_trail : public trail<context> {
bool_var m_var;
public:
set_true_first_trail(bool_var v):m_var(v) {}
virtual void undo(context & ctx) {
ctx.m_bdata[m_var].reset_true_first_flag();
}
};
void context::set_true_first_flag(bool_var v) {
push_trail(set_true_first_trail(v));
bool_var_data & d = m_bdata[v];
d.set_true_first_flag();
}
bool context::assume_eq(enode * lhs, enode * rhs) {
if (lhs->get_root() == rhs->get_root())
return false; // it is not necessary to assume the eq.
expr * _lhs = lhs->get_owner();
expr * _rhs = rhs->get_owner();
expr * eq = mk_eq_atom(_lhs, _rhs);
TRACE("assume_eq", tout << "creating interface eq:\n" << mk_pp(eq, m_manager) << "\n";);
if (m_manager.is_false(eq)) {
return false;
}
bool r = false;
if (!b_internalized(eq)) {
// I do not invoke internalize(eq, true), because I want to
// mark the try_true_first flag before invoking theory::internalize_eq_eh.
// Reason: Theories like arithmetic should be able to know if the try_true_first flag is
// marked or not. They use this information to also mark auxiliary atoms such as:
// (<= (- x y) 0)
// (>= (- y x) 0)
// for the new equality atom (= x y).
if (m_manager.is_eq(eq)) {
internalize_formula_core(to_app(eq), true);
bool_var v = get_bool_var(eq);
bool_var_data & d = get_bdata(v);
d.set_eq_flag();
set_true_first_flag(v);
sort * s = m_manager.get_sort(to_app(eq)->get_arg(0));
theory * th = m_theories.get_plugin(s->get_family_id());
if (th)
th->internalize_eq_eh(to_app(eq), v);
}
else {
internalize(eq, true);
}
r = true;
m_stats.m_num_interface_eqs++;
TRACE("assume_eq", tout << "new internalization.\n";);
}
bool_var v = get_bool_var(eq);
bool_var_data & d = m_bdata[v];
if (!d.try_true_first()) {
set_true_first_flag(v);
r = true;
TRACE("assume_eq", tout << "marked as ieq.\n";);
}
if (get_assignment(v) == l_undef) {
TRACE("assume_eq", tout << "variable is unassigned.\n";);
r = true;
}
if (relevancy()) {
if (!is_relevant(eq)) {
TRACE("assume_eq", tout << "marking eq as relevant.\n";);
mark_as_relevant(eq);
r = true;
}
}
TRACE("assume_eq", tout << "variable value: " << get_assignment(v) << "\n";);
TRACE("assume_eq", tout << "assume_eq result: " << r << "\n";);
return r;
}
bool context::is_shared(enode * n) const {
n = n->get_root();
unsigned num_th_vars = n->get_num_th_vars();
switch (num_th_vars) {
case 0:
return false;
case 1: {
if (m_qmanager->is_shared(n))
return true;
// the variabe is shared if the equivalence class of n
// contains a parent application.
theory_var_list * l = n->get_th_var_list();
theory_id th_id = l->get_th_id();
enode_vector::const_iterator it = n->begin_parents();
enode_vector::const_iterator end = n->end_parents();
for (; it != end; ++it) {
enode * parent = *it;
family_id fid = parent->get_owner()->get_family_id();
if (fid != th_id && fid != m_manager.get_basic_family_id()) {
TRACE("is_shared", tout << mk_pp(n->get_owner(), m_manager) << "\nis shared because of:\n" << mk_pp(parent->get_owner(), m_manager) << "\n";);
return true;
}
}
// Some theories implement families of theories. Examples:
// Arrays and Tuples. For example, array theory is a
// parametric theory, that is, it implements several theories:
// (array int int), (array int (array int int)), ...
//
// Example:
//
// a : (array int int)
// b : (array int int)
// x : int
// y : int
// v : int
// w : int
// A : (array (array int int) int)
//
// assert (= b (store a x v))
// assert (= b (store a y w))
// assert (not (= x y))
// assert (not (select A a))
// assert (not (select A b))
// check
//
// In the example above, 'a' and 'b' are shared variables between
// the theories of (array int int) and (array (array int int) int).
// Remark: The inconsistency is not going to be detected if they are
// not marked as shared.
return get_theory(th_id)->is_shared(l->get_th_var());
}
default:
return true;
}
}
bool context::get_value(enode * n, expr_ref & value) {
sort * s = m_manager.get_sort(n->get_owner());
family_id fid = s->get_family_id();
theory * th = get_theory(fid);
if (th == 0)
return false;
return th->get_value(n, value);
}
void context::mk_proto_model(lbool r) {
TRACE("get_model",
display(tout);
display_normalized_enodes(tout);
display_enodes_lbls(tout);
m_fingerprints.display(tout);
);
failure fl = get_last_search_failure();
if (fl == TIMEOUT || fl == MEMOUT || fl == CANCELED || fl == NUM_CONFLICTS) {
// don't generate model.
return;
}
if (m_fparams.m_model || m_fparams.m_model_on_final_check || m_qmanager->model_based()) {
m_model_generator->reset();
m_proto_model = m_model_generator->mk_model();
m_qmanager->adjust_model(m_proto_model.get());
TRACE("mbqi_bug", tout << "before complete_partial_funcs:\n"; model_pp(tout, *m_proto_model););
m_proto_model->complete_partial_funcs();
TRACE("mbqi_bug", tout << "before cleanup:\n"; model_pp(tout, *m_proto_model););
m_proto_model->cleanup();
if (m_fparams.m_model_compact)
m_proto_model->compress();
TRACE("mbqi_bug", tout << "after cleanup:\n"; model_pp(tout, *m_proto_model););
}
}
proof * context::get_proof() {
if (!m_manager.proofs_enabled())
return 0;
return m_unsat_proof;
}
void context::get_model(model_ref & m) const {
if (inconsistent())
m = 0;
else
m = const_cast<model*>(m_model.get());
}
void context::get_proto_model(proto_model_ref & m) const {
m = const_cast<proto_model*>(m_proto_model.get());
}
failure context::get_last_search_failure() const {
return m_last_search_failure;
}
void context::set_cancel_flag(bool f) {
m_cancel_flag = f;
m_asserted_formulas.set_cancel_flag(f);
}
};