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

420 lines
15 KiB
C++

/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
smt_model_checker.cpp
Abstract:
Model checker
Author:
Leonardo de Moura (leonardo) 2010-12-03.
Revision History:
--*/
#include"smt_model_checker.h"
#include"smt_context.h"
#include"smt_model_finder.h"
#include"pull_quant.h"
#include"for_each_expr.h"
#include"var_subst.h"
#include"ast_pp.h"
#include"ast_ll_pp.h"
#include"model_pp.h"
#include"ast_smt2_pp.h"
namespace smt {
model_checker::model_checker(ast_manager & m, qi_params const & p, model_finder & mf):
m_manager(m),
m_params(p),
m_qm(0),
m_context(0),
m_root2value(0),
m_model_finder(mf),
m_max_cexs(1),
m_iteration_idx(0),
m_curr_model(0),
m_new_instances_bindings(m) {
}
model_checker::~model_checker() {
m_aux_context = 0; // delete aux context before fparams
m_fparams = 0;
}
quantifier * model_checker::get_flat_quantifier(quantifier * q) {
return m_model_finder.get_flat_quantifier(q);
}
void model_checker::set_qm(quantifier_manager & qm) {
SASSERT(m_qm == 0);
SASSERT(m_context == 0);
m_qm = &qm;
m_context = &(m_qm->kernel());
}
/**
\brief Return a term in the context that evaluates to val.
*/
expr * model_checker::get_term_from_ctx(expr * val) {
if (m_value2expr.empty()) {
// populate m_value2expr
obj_map<enode, app *>::iterator it = m_root2value->begin();
obj_map<enode, app *>::iterator end = m_root2value->end();
for (; it != end; ++it) {
enode * n = (*it).m_key;
expr * val = (*it).m_value;
n = n->get_eq_enode_with_min_gen();
m_value2expr.insert(val, n->get_owner());
}
}
expr * t = 0;
m_value2expr.find(val, t);
return t;
}
/**
\brief Assert in m_aux_context, the constraint
sk = e_1 OR ... OR sk = e_n
where {e_1, ..., e_n} is the universe.
*/
void model_checker::restrict_to_universe(expr * sk, obj_hashtable<expr> const & universe) {
SASSERT(!universe.empty());
ptr_buffer<expr> eqs;
obj_hashtable<expr>::iterator it = universe.begin();
obj_hashtable<expr>::iterator end = universe.end();
for (; it != end; ++it) {
expr * e = *it;
eqs.push_back(m_manager.mk_eq(sk, e));
}
m_aux_context->assert_expr(m_manager.mk_or(eqs.size(), eqs.c_ptr()));
}
#define PP_DEPTH 8
/**
\brief Assert the negation of q after applying the interpretation in m_curr_model to the uninterpreted symbols in q.
The variables are replaced by skolem constants. These constants are stored in sks.
*/
void model_checker::assert_neg_q_m(quantifier * q, expr_ref_vector & sks) {
expr_ref tmp(m_manager);
m_curr_model->eval(q->get_expr(), tmp, true);
TRACE("model_checker", tout << "q after applying interpretation:\n" << mk_ismt2_pp(tmp, m_manager) << "\n";);
ptr_buffer<expr> subst_args;
unsigned num_decls = q->get_num_decls();
subst_args.resize(num_decls, 0);
sks.resize(num_decls, 0);
for (unsigned i = 0; i < num_decls; i++) {
sort * s = q->get_decl_sort(num_decls - i - 1);
expr * sk = m_manager.mk_fresh_const(0, s);
sks[num_decls - i - 1] = sk;
subst_args[num_decls - i - 1] = sk;
if (m_curr_model->is_finite(s)) {
restrict_to_universe(sk, m_curr_model->get_known_universe(s));
}
}
expr_ref sk_body(m_manager);
var_subst s(m_manager);
s(tmp, subst_args.size(), subst_args.c_ptr(), sk_body);
expr_ref r(m_manager);
r = m_manager.mk_not(sk_body);
TRACE("model_checker", tout << "mk_neg_q_m:\n" << mk_ismt2_pp(r, m_manager) << "\n";);
m_aux_context->assert_expr(r);
}
bool model_checker::add_instance(quantifier * q, model * cex, expr_ref_vector & sks, bool use_inv) {
if (cex == 0)
return false; // no model available.
unsigned num_decls = q->get_num_decls();
unsigned num_sks = sks.size();
// Remark: sks were created for the flat version of q.
SASSERT(num_sks >= num_decls);
expr_ref_buffer bindings(m_manager);
bindings.resize(num_decls);
unsigned max_generation = 0;
for (unsigned i = 0; i < num_decls; i++) {
expr * sk = sks.get(num_decls - i - 1);
func_decl * sk_d = to_app(sk)->get_decl();
expr_ref sk_value(m_manager);
sk_value = cex->get_const_interp(sk_d);
if (sk_value == 0) {
sk_value = cex->get_some_value(sk_d->get_range());
if (sk_value == 0)
return false; // get_some_value failed... giving up
}
if (use_inv) {
unsigned sk_term_gen;
expr * sk_term = m_model_finder.get_inv(q, i, sk_value, sk_term_gen);
if (sk_term != 0) {
SASSERT(!m_manager.is_model_value(sk_term));
if (sk_term_gen > max_generation)
max_generation = sk_term_gen;
sk_value = sk_term;
}
else {
return false;
}
}
else {
expr * sk_term = get_term_from_ctx(sk_value);
if (sk_term != 0) {
sk_value = sk_term;
}
else {
if (m_manager.is_model_value(sk_value))
return false;
}
}
bindings.set(num_decls - i - 1, sk_value);
}
TRACE("model_checker", tout << q->get_qid() << " found (use_inv: " << use_inv << ") new instance:\n";
for (unsigned i = 0; i < num_decls; i++) {
tout << mk_ismt2_pp(bindings[i], m_manager) << "\n";
});
for (unsigned i = 0; i < num_decls; i++)
m_new_instances_bindings.push_back(bindings[i]);
void * mem = m_new_instances_region.allocate(instance::get_obj_size(q->get_num_decls()));
instance * new_inst = new (mem) instance(q, bindings.c_ptr(), max_generation);
m_new_instances.push_back(new_inst);
return true;
}
bool model_checker::add_blocking_clause(model * cex, expr_ref_vector & sks) {
SASSERT(cex != 0);
unsigned num_sks = sks.size();
expr_ref_buffer diseqs(m_manager);
for (unsigned i = 0; i < num_sks; i++) {
expr * sk = sks.get(i);
func_decl * sk_d = to_app(sk)->get_decl();
expr_ref sk_value(m_manager);
sk_value = cex->get_const_interp(sk_d);
if (sk_value == 0) {
sk_value = cex->get_some_value(sk_d->get_range());
if (sk_value == 0)
return false; // get_some_value failed... aborting add_blocking_clause
}
diseqs.push_back(m_manager.mk_not(m_manager.mk_eq(sk, sk_value)));
}
expr_ref blocking_clause(m_manager);
blocking_clause = m_manager.mk_or(diseqs.size(), diseqs.c_ptr());
TRACE("model_checker", tout << "blocking clause:\n" << mk_ismt2_pp(blocking_clause, m_manager) << "\n";);
m_aux_context->assert_expr(blocking_clause);
return true;
}
/**
\brief Return true if q is satisfied by m_curr_model.
*/
bool model_checker::check(quantifier * q) {
SASSERT(!m_aux_context->relevancy());
m_aux_context->push();
quantifier * flat_q = get_flat_quantifier(q);
TRACE("model_checker", tout << "model checking:\n" << mk_ismt2_pp(q->get_expr(), m_manager) << "\n" <<
mk_ismt2_pp(flat_q->get_expr(), m_manager) << "\n";);
expr_ref_vector sks(m_manager);
assert_neg_q_m(flat_q, sks);
TRACE("model_checker", tout << "skolems:\n";
for (unsigned i = 0; i < sks.size(); i++) {
expr * sk = sks.get(i);
tout << mk_ismt2_pp(sk, m_manager) << " " << mk_pp(m_manager.get_sort(sk), m_manager) << "\n";
});
lbool r = m_aux_context->check();
TRACE("model_checker", tout << "[complete] model-checker result: " << to_sat_str(r) << "\n";);
if (r == l_false) {
m_aux_context->pop(1);
return true; // quantifier is satisfied by m_curr_model
}
model_ref complete_cex;
m_aux_context->get_model(complete_cex);
// try to find new instances using instantiation sets.
m_model_finder.restrict_sks_to_inst_set(m_aux_context.get(), q, sks);
unsigned num_new_instances = 0;
while (true) {
lbool r = m_aux_context->check();
TRACE("model_checker", tout << "[restricted] model-checker (" << (num_new_instances+1) << ") result: " << to_sat_str(r) << "\n";);
if (r == l_false)
break;
model_ref cex;
m_aux_context->get_model(cex);
if (add_instance(q, cex.get(), sks, true)) {
num_new_instances++;
if (num_new_instances < m_max_cexs) {
if (!add_blocking_clause(cex.get(), sks))
break; // add_blocking_clause failed... stop the search for new counter-examples...
}
}
else {
break;
}
if (num_new_instances >= m_max_cexs)
break;
}
if (num_new_instances == 0) {
// failed to create instances when restricting to inst sets... then use result of the complete model check
TRACE("model_checker", tout << "using complete_cex result:\n"; model_pp(tout, *complete_cex););
add_instance(q, complete_cex.get(), sks, false);
}
m_aux_context->pop(1);
return false;
}
void model_checker::init_aux_context() {
if (!m_fparams) {
m_fparams = alloc(front_end_params, m_context->get_fparams());
m_fparams->m_relevancy_lvl = 0; // no relevancy since the model checking problems are quantifier free
}
if (!m_aux_context) {
symbol logic;
m_aux_context = m_context->mk_fresh(&logic, m_fparams.get());
}
}
struct scoped_set_relevancy {
};
bool model_checker::check(proto_model * md, obj_map<enode, app *> const & root2value) {
SASSERT(md != 0);
m_root2value = &root2value;
ptr_vector<quantifier>::const_iterator it = m_qm->begin_quantifiers();
ptr_vector<quantifier>::const_iterator end = m_qm->end_quantifiers();
if (it == end)
return true;
if (m_iteration_idx >= m_params.m_mbqi_max_iterations)
return false;
m_curr_model = md;
m_value2expr.reset();
md->compress();
TRACE("model_checker", tout << "MODEL_CHECKER INVOKED\n";
tout << "model:\n"; model_pp(tout, *m_curr_model););
if (m_params.m_mbqi_trace) {
verbose_stream() << "[mbqi] started\n";
}
init_aux_context();
bool found_relevant = false;
unsigned num_failures = 0;
for (; it != end; ++it) {
quantifier * q = *it;
if (m_context->is_relevant(q) && m_context->get_assignment(q) == l_true) {
if (m_params.m_mbqi_trace && q->get_qid() != symbol::null) {
verbose_stream() << "[mbqi] checking: " << q->get_qid() << "\n";
}
found_relevant = true;
if (!check(q)) {
IF_VERBOSE(5, verbose_stream() << "current model does not satisfy: " << q->get_qid() << "\n";);
if (m_params.m_mbqi_trace) {
verbose_stream() << "[mbqi] failed " << q->get_qid() << "\n";
}
num_failures++;
}
}
}
if (found_relevant)
m_iteration_idx++;
TRACE("model_checker", tout << "model after check:\n"; model_pp(tout, *md););
TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";);
m_max_cexs += m_params.m_mbqi_max_cexs;
if (num_failures == 0)
m_curr_model->cleanup();
if (m_params.m_mbqi_trace) {
if (num_failures == 0)
verbose_stream() << "[mbqi] succeeded\n";
else
verbose_stream() << "[mbqi] num failures " << num_failures << "\n";
}
return num_failures == 0;
}
void model_checker::init_search_eh() {
m_max_cexs = m_params.m_mbqi_max_cexs;
m_iteration_idx = 0;
}
void model_checker::restart_eh() {
IF_VERBOSE(100, verbose_stream() << "instantiating new instances...\n";);
assert_new_instances();
reset_new_instances();
}
bool model_checker::has_new_instances() {
return !m_new_instances.empty();
}
void model_checker::reset_new_instances() {
m_new_instances_region.reset();
m_new_instances.reset();
}
void model_checker::reset() {
reset_new_instances();
}
void model_checker::assert_new_instances() {
TRACE("model_checker_bug_detail", tout << "assert_new_instances, inconsistent: " << m_context->inconsistent() << "\n";);
ptr_buffer<enode> bindings;
ptr_vector<enode> dummy;
ptr_vector<instance>::iterator it = m_new_instances.begin();
ptr_vector<instance>::iterator end = m_new_instances.end();
for (; it != end; ++it) {
instance * inst = *it;
quantifier * q = inst->m_q;
if (m_context->b_internalized(q)) {
bindings.reset();
unsigned num_decls = q->get_num_decls();
unsigned gen = inst->m_generation;
for (unsigned i = 0; i < num_decls; i++) {
expr * b = inst->m_bindings[i];
if (!m_context->e_internalized(b)) {
TRACE("model_checker_bug_detail", tout << "internalizing b:\n" << mk_pp(b, m_manager) << "\n";);
m_context->internalize(b, false, gen);
}
bindings.push_back(m_context->get_enode(b));
}
TRACE("model_checker_bug_detail", tout << "instantiating... q:\n" << mk_pp(q, m_manager) << "\n";
tout << "inconsistent: " << m_context->inconsistent() << "\n";
tout << "bindings:\n";
for (unsigned i = 0; i < num_decls; i++) {
expr * b = inst->m_bindings[i];
tout << mk_pp(b, m_manager) << "\n";
});
TRACE("model_checker_instance",
expr_ref inst_expr(m_manager);
instantiate(m_manager, q, inst->m_bindings, inst_expr);
tout << "(assert " << mk_ismt2_pp(inst_expr, m_manager) << ")\n";);
m_context->add_instance(q, 0, num_decls, bindings.c_ptr(), gen, gen, gen, dummy);
TRACE("model_checker_bug_detail", tout << "after instantiating, inconsistent: " << m_context->inconsistent() << "\n";);
}
}
}
};