Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
z3-z3-4.12.6.src.qe.qsat.cpp Maven / Gradle / Ivy
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
qsat.cpp
Abstract:
Quantifier Satisfiability Solver.
Author:
Nikolaj Bjorner (nbjorner) 2015-5-28
Revision History:
Notes:
--*/
#include "ast/expr_abstract.h"
#include "ast/ast_util.h"
#include "ast/occurs.h"
#include "ast/rewriter/quant_hoist.h"
#include "ast/ast_pp.h"
#include "ast/rewriter/th_rewriter.h"
#include "ast/rewriter/expr_replacer.h"
#include "model/model_v2_pp.h"
#include "model/model_evaluator.h"
#include "model/model_evaluator_params.hpp"
#include "smt/smt_kernel.h"
#include "smt/params/smt_params.h"
#include "smt/smt_solver.h"
#include "solver/solver.h"
#include "solver/mus.h"
#include "qe/qsat.h"
#include "qe/qe_mbp.h"
#include "qe/qe.h"
#include "ast/rewriter/label_rewriter.h"
#include "util/params.h"
namespace qe {
pred_abs::pred_abs(ast_manager& m):
m(m),
m_asms(m),
m_trail(m),
m_fmc(alloc(generic_model_converter, m, "qsat"))
{
}
generic_model_converter* pred_abs::fmc() {
return m_fmc.get();
}
void pred_abs::reset() {
m_trail.reset();
dec_keys(m_pred2lit);
dec_keys(m_lit2pred);
dec_keys(m_asm2pred);
dec_keys(m_pred2asm);
m_lit2pred.reset();
m_pred2lit.reset();
m_asm2pred.reset();
m_pred2asm.reset();
m_elevel.reset();
m_asms.reset();
m_asms_lim.reset();
m_preds.reset();
}
max_level pred_abs::compute_level(app* e) {
unsigned sz0 = todo.size();
todo.push_back(e);
while (sz0 != todo.size()) {
app* a = to_app(todo.back());
if (m_elevel.contains(a)) {
todo.pop_back();
continue;
}
max_level lvl, lvl0;
bool has_new = false;
if (m_flevel.find(a->get_decl(), lvl)) {
lvl0.merge(lvl);
}
for (expr* f : *a) {
if (!is_app(f))
throw tactic_exception("atom is non-ground");
app* arg = to_app(f);
if (m_elevel.find(arg, lvl)) {
lvl0.merge(lvl);
}
else {
todo.push_back(arg);
has_new = true;
}
}
if (!has_new) {
m_elevel.insert(a, lvl0);
todo.pop_back();
}
}
return m_elevel.find(e);
}
void pred_abs::add_pred(app* p, app* lit) {
m.inc_ref(p);
m_pred2lit.insert(p, lit);
add_lit(p, lit);
}
void pred_abs::add_lit(app* p, app* lit) {
if (!m_lit2pred.contains(lit)) {
m.inc_ref(lit);
m_lit2pred.insert(lit, p);
}
}
void pred_abs::add_asm(app* p, expr* assum) {
SASSERT(!m_asm2pred.contains(assum));
m.inc_ref(p);
m.inc_ref(assum);
m_asm2pred.insert(assum, p);
m_pred2asm.insert(p, assum);
}
void pred_abs::push() {
m_asms_lim.push_back(m_asms.size());
}
void pred_abs::pop(unsigned num_scopes) {
unsigned l = m_asms_lim.size() - num_scopes;
m_asms.resize(m_asms_lim[l]);
m_asms_lim.shrink(l);
}
void pred_abs::insert(app* a, max_level const& lvl) {
unsigned l = lvl.max();
if (l == UINT_MAX) l = 0;
while (m_preds.size() <= l) {
m_preds.push_back(app_ref_vector(m));
}
m_preds[l].push_back(a);
}
bool pred_abs::is_predicate(app* a, unsigned l) {
max_level lvl1;
return m_flevel.find(a->get_decl(), lvl1) && lvl1.max() < l;
}
void pred_abs::get_assumptions(model* mdl, expr_ref_vector& asms) {
unsigned level = m_asms_lim.size();
if (level > m_preds.size()) {
level = m_preds.size();
}
if (!mdl) {
asms.append(m_asms);
return;
}
if (level == 0) {
return;
}
model_evaluator eval(*mdl);
eval.set_model_completion(true);
TRACE("qe_assumptions", model_v2_pp(tout, *mdl););
expr_ref val(m);
for (unsigned i = 0; i <= level-1; ++i) {
for (unsigned j = 0; j < m_preds[i].size(); ++j) {
app* p = m_preds[i][j].get();
eval(p, val);
if (!m.inc())
return;
if (m.is_false(val)) {
m_asms.push_back(m.mk_not(p));
}
else {
SASSERT(m.is_true(val));
m_asms.push_back(p);
}
}
}
asms.append(m_asms);
for (unsigned i = level + 1; i < m_preds.size(); i += 2) {
for (unsigned j = 0; j < m_preds[i].size(); ++j) {
if (!m.inc())
return;
app* p = m_preds[i][j].get();
max_level lvl = m_elevel.find(p);
bool use =
(lvl.m_fa == i && (lvl.m_ex == UINT_MAX || lvl.m_ex < level)) ||
(lvl.m_ex == i && (lvl.m_fa == UINT_MAX || lvl.m_fa < level));
if (use) {
eval(p, val);
if (m.is_false(val)) {
asms.push_back(m.mk_not(p));
}
else {
asms.push_back(p);
}
}
}
}
TRACE("qe_assumptions", tout << "level: " << level << "\n";
model_v2_pp(tout, *mdl);
display(tout, asms););
}
void pred_abs::ensure_expr_level(app* v, unsigned lvl) {
if (!m_elevel.contains(v)) {
max_level ml;
if (lvl % 2 == 0) ml.m_ex = lvl; else ml.m_fa = lvl;
m_elevel.insert(v, ml);
}
}
void pred_abs::set_expr_level(app* v, max_level const& lvl) {
m_elevel.insert(v, lvl);
}
void pred_abs::set_decl_level(func_decl* f, max_level const& lvl) {
m_flevel.insert(f, lvl);
}
void pred_abs::abstract_atoms(expr* fml, expr_ref_vector& defs) {
max_level level;
abstract_atoms(fml, level, defs);
}
/**
\brief create propositional abstraction of formula by replacing atomic sub-formulas by fresh
propositional variables, and adding definitions for each propositional formula on the side.
Assumption is that the formula is quantifier-free.
*/
void pred_abs::abstract_atoms(expr* fml, max_level& level, expr_ref_vector& defs) {
expr_mark mark;
ptr_vector args;
app_ref r(m), eq(m);
app* p;
unsigned sz0 = todo.size();
todo.push_back(fml);
m_trail.push_back(fml);
max_level l;
while (sz0 != todo.size()) {
app* a = to_app(todo.back());
todo.pop_back();
if (mark.is_marked(a))
continue;
mark.mark(a);
if (m_lit2pred.find(a, p)) {
TRACE("qe", tout << mk_pp(a, m) << " " << mk_pp(p, m) << "\n";);
level.merge(m_elevel.find(p));
continue;
}
if (is_uninterp_const(a) && m.is_bool(a)) {
TRACE("qe", tout << mk_pp(a, m) << "\n";);
l = m_elevel.find(a);
level.merge(l);
if (!m_pred2lit.contains(a)) {
add_pred(a, a);
insert(a, l);
}
continue;
}
for (expr* f : *a)
if (!mark.is_marked(f))
todo.push_back(f);
bool is_boolop =
(a->get_family_id() == m.get_basic_family_id()) &&
(!m.is_eq(a) || m.is_bool(a->get_arg(0))) &&
(!m.is_distinct(a) || a->get_num_args() == 0 || m.is_bool(a->get_arg(0)));
if (!is_boolop && m.is_bool(a)) {
TRACE("qe", tout << mk_pp(a, m) << "\n";);
r = fresh_bool("p");
max_level l = compute_level(a);
add_pred(r, a);
m_elevel.insert(r, l);
eq = m.mk_eq(r, a);
defs.push_back(eq);
if (!is_predicate(a, l.max()))
insert(r, l);
level.merge(l);
}
}
}
app_ref pred_abs::fresh_bool(char const* name) {
app_ref r(m.mk_fresh_const(name, m.mk_bool_sort(), true), m);
m_fmc->hide(r);
return r;
}
// optional pass to replace atoms by predicates
// so that SMT core works on propositional
// abstraction only.
expr_ref pred_abs::mk_abstract(expr* fml) {
expr_ref_vector trail(m), args(m);
obj_map cache;
app* b;
expr_ref r(m);
unsigned sz0 = todo.size();
todo.push_back(fml);
while (sz0 != todo.size()) {
app* a = to_app(todo.back());
if (cache.contains(a)) {
todo.pop_back();
continue;
}
if (m_lit2pred.find(a, b)) {
cache.insert(a, b);
todo.pop_back();
continue;
}
unsigned sz = a->get_num_args();
bool diff = false;
args.reset();
for (expr* f : *a) {
expr *f1;
if (cache.find(f, f1)) {
args.push_back(f1);
diff |= f != f1;
}
else {
todo.push_back(f);
}
}
if (sz == args.size()) {
if (diff) {
r = m.mk_app(a->get_decl(), args);
trail.push_back(r);
}
else {
r = a;
}
cache.insert(a, r);
todo.pop_back();
}
}
return expr_ref(cache.find(fml), m);
}
expr_ref pred_abs::mk_assumption_literal(expr* a, model* mdl, max_level const& lvl, expr_ref_vector& defs) {
expr_ref A(m);
A = pred2asm(a);
a = A;
app_ref p(m);
expr_ref q(m), fml(m);
app *b;
expr *c, *d;
max_level lvl2;
TRACE("qe", tout << mk_pp(a, m) << " " << lvl << "\n";);
if (m_asm2pred.find(a, b))
q = b;
else if (m.is_not(a, c) && m_asm2pred.find(c, b))
q = m.mk_not(b);
else if (m_pred2asm.find(a, d))
q = a;
else if (m.is_not(a, c) && m_pred2asm.find(c, d))
q = a;
else {
p = fresh_bool("def");
if (m.is_not(a, a)) {
if (mdl)
mdl->register_decl(p->get_decl(), m.mk_false());
q = m.mk_not(p);
}
else {
if (mdl)
mdl->register_decl(p->get_decl(), m.mk_true());
q = p;
}
m_elevel.insert(p, lvl);
insert(p, lvl);
fml = a;
abstract_atoms(fml, lvl2, defs);
fml = mk_abstract(fml);
defs.push_back(m.mk_eq(p, fml));
add_asm(p, a);
TRACE("qe", tout << mk_pp(a, m) << " |-> " << p << "\n";);
}
return q;
}
void pred_abs::mk_concrete(expr_ref_vector& fmls, obj_map const& map) {
obj_map cache;
expr_ref_vector trail(fmls);
expr* p;
app_ref r(m);
ptr_vector args;
unsigned sz0 = todo.size();
todo.append(fmls.size(), (expr*const*)fmls.data());
while (sz0 != todo.size()) {
app* a = to_app(todo.back());
if (cache.contains(a)) {
todo.pop_back();
continue;
}
if (map.find(a, p)) {
cache.insert(a, p);
todo.pop_back();
continue;
}
unsigned sz = a->get_num_args();
args.reset();
bool diff = false;
for (expr* f : *a) {
expr *f1;
if (cache.find(f, f1)) {
args.push_back(f1);
diff |= f != f1;
}
else {
todo.push_back(f);
}
}
if (args.size() == sz) {
if (diff)
r = m.mk_app(a->get_decl(), args);
else
r = to_app(a);
cache.insert(a, r);
trail.push_back(r);
todo.pop_back();
}
}
for (unsigned i = 0; i < fmls.size(); ++i)
fmls[i] = to_app(cache.find(fmls.get(i)));
}
void pred_abs::pred2lit(expr_ref_vector& fmls) {
mk_concrete(fmls, m_pred2lit);
}
expr_ref pred_abs::pred2asm(expr* fml) {
expr_ref_vector fmls(m);
fmls.push_back(fml);
mk_concrete(fmls, m_pred2asm);
return mk_and(fmls);
}
void pred_abs::collect_statistics(statistics& st) const {
st.update("qsat num predicates", m_pred2lit.size());
}
void pred_abs::display(std::ostream& out) const {
out << "pred2lit:\n";
for (auto const& kv : m_pred2lit) {
out << mk_pp(kv.m_key, m) << " |-> " << mk_pp(kv.m_value, m) << "\n";
}
for (unsigned i = 0; i < m_preds.size(); ++i) {
out << "level " << i << "\n";
for (unsigned j = 0; j < m_preds[i].size(); ++j) {
app* p = m_preds[i][j];
expr* e;
if (m_pred2lit.find(p, e))
out << mk_pp(p, m) << " := " << mk_pp(e, m) << "\n";
else
out << mk_pp(p, m) << "\n";
}
}
}
void pred_abs::display(std::ostream& out, expr_ref_vector const& asms) const {
max_level lvl;
for (expr* a : asms) {
expr* e = a;
bool is_not = m.is_not(a, e);
out << mk_pp(a, m);
if (m_elevel.find(e, lvl))
lvl.display(out << " - ");
if (m_pred2lit.find(e, e))
out << " : " << (is_not?"!":"") << mk_pp(e, m);
out << "\n";
}
}
void pred_abs::get_free_vars(expr* fml, app_ref_vector& vars) {
ast_fast_mark1 mark;
unsigned sz0 = todo.size();
todo.push_back(fml);
while (sz0 != todo.size()) {
expr* e = todo.back();
todo.pop_back();
if (mark.is_marked(e) || is_var(e))
continue;
mark.mark(e);
if (is_quantifier(e)) {
todo.push_back(to_quantifier(e)->get_expr());
continue;
}
SASSERT(is_app(e));
app* a = to_app(e);
if (is_uninterp_const(a)) // TBD generalize for uninterpreted functions.
vars.push_back(a);
for (expr* arg : *a)
todo.push_back(arg);
}
}
bool pred_abs::validate_defs(model& model) const {
bool valid = true;
for (auto& kv : m_pred2lit) {
expr_ref val_a(m), val_b(m);
expr* a = kv.m_key;
expr* b = kv.m_value;
val_a = model(a);
val_b = model(b);
if ((m.is_true(val_a) && m.is_false(val_b)) ||
(m.is_false(val_a) && m.is_true(val_b))) {
TRACE("qe",
tout << model << "\n";
tout << mk_pp(a, m) << " := " << val_a << "\n";
tout << mk_pp(b, m) << " := " << val_b << "\n";
tout << m_elevel.find(a) << "\n";);
valid = false;
}
}
return valid;
}
class kernel {
ast_manager& m;
params_ref m_params;
ref m_solver;
expr_ref m_last_assert;
public:
kernel(ast_manager& m):
m(m),
m_solver(nullptr),
m_last_assert(m)
{
m_params.set_bool("model", true);
m_params.set_uint("relevancy", 0);
m_params.set_uint("case_split_strategy", CS_ACTIVITY_WITH_CACHE);
}
solver& s() { return *m_solver; }
solver const& s() const { return *m_solver; }
void init() {
m_solver = mk_smt2_solver(m, m_params, symbol::null);
m_last_assert = nullptr;
}
void collect_statistics(statistics & st) const {
if (m_solver)
m_solver->collect_statistics(st);
}
void reset_statistics() {
clear();
}
void collect_statistics(statistics& st) {
if (m_solver)
m_solver->collect_statistics(st);
}
void clear() {
m_solver = nullptr;
}
void assert_expr(expr* e) {
if (!m.is_true(e))
m_solver->assert_expr(e);
}
void assert_blocking_fml(expr* e) {
if (m.is_true(e))
return;
if (m_last_assert && e == m_last_assert && !m.is_false(e)) {
IF_VERBOSE(0, verbose_stream() << "Asserting this expression twice in a row:\n " << m_last_assert << "\n");
UNREACHABLE();
}
m_last_assert = e;
m_solver->assert_expr(e);
}
void get_core(expr_ref_vector& core) {
core.reset();
m_solver->get_unsat_core(core);
TRACE("qe_core", m_solver->display(tout << "core: " << core << "\n") << "\n";);
}
};
enum qsat_mode {
qsat_qe,
qsat_qe_rec,
qsat_sat,
qsat_maximize
};
class qsat : public tactic {
struct stats {
unsigned m_num_rounds;
stats() { reset(); }
void reset() { memset(this, 0, sizeof(*this)); }
};
ast_manager& m;
params_ref m_params;
stats m_stats;
statistics m_st;
qe::mbproj m_mbp;
kernel m_fa;
kernel m_ex;
pred_abs m_pred_abs;
expr_ref_vector m_answer;
expr_ref_vector m_asms;
vector m_vars; // variables from alternating prefixes.
unsigned m_level;
model_ref m_model;
qsat_mode m_mode;
app_ref_vector m_avars; // variables to project
app_ref_vector m_free_vars;
app* m_objective;
opt::inf_eps* m_value;
bool m_was_sat;
model_ref m_model_save;
expr_ref m_gt;
opt::inf_eps m_value_save;
/**
\brief check alternating satisfiability.
Even levels are existential, odd levels are universal.
*/
lbool check_sat() {
while (true) {
++m_stats.m_num_rounds;
IF_VERBOSE(1, verbose_stream() << "(check-qsat level: " << m_level << " round: " << m_stats.m_num_rounds << ")\n";);
TRACE("qe",
tout << "level: " << m_level << " round: " << m_stats.m_num_rounds << "\n");
check_cancel();
expr_ref_vector asms(m_asms);
m_pred_abs.get_assumptions(m_model.get(), asms);
if (m_model.get())
validate_assumptions(*m_model.get(), asms);
TRACE("qe", tout << asms << "\n";);
solver& s = get_kernel(m_level).s();
lbool res = s.check_sat(asms);
switch (res) {
case l_true:
s.get_model(m_model);
CTRACE("qe", !m_model, tout << "no model\n");
if (!m_model)
return l_undef;
SASSERT(validate_defs("check_sat"));
SASSERT(!m_model.get() || validate_assumptions(*m_model.get(), asms));
SASSERT(validate_model(asms));
TRACE("qe", s.display(tout); display(tout << "\n", *m_model.get()); display(tout, asms); );
if (m_level == 0)
m_model_save = m_model;
push();
if (m_level == 1 && m_mode == qsat_maximize)
maximize_model();
break;
case l_false:
switch (m_level) {
case 0:
return l_false;
case 1:
if (m_mode == qsat_sat)
return l_true;
if (m_model.get()) {
SASSERT(validate_assumptions(*m_model.get(), asms));
if (!project_qe(asms))
return l_undef;
}
else
pop(1);
break;
default:
if (m_model.get()) {
if (!project(asms))
return l_undef;
}
else
pop(1);
break;
}
break;
case l_undef:
TRACE("qe", tout << "check-sat is undef\n");
return res;
}
}
return l_undef;
}
kernel& get_kernel(unsigned j) {
if (is_exists(j)) {
return m_ex;
}
else {
return m_fa;
}
}
bool is_exists(unsigned level) const {
return (level % 2) == 0;
}
bool is_forall(unsigned level) const {
return is_exists(level+1);
}
void push() {
m_level++;
m_pred_abs.push();
}
void pop(unsigned num_scopes) {
m_model.reset();
SASSERT(num_scopes <= m_level);
m_pred_abs.pop(num_scopes);
m_level -= num_scopes;
}
void clear() {
m_st.reset();
m_fa.collect_statistics(m_st);
m_ex.collect_statistics(m_st);
m_pred_abs.collect_statistics(m_st);
m_level = 0;
m_answer.reset();
m_asms.reset();
m_pred_abs.reset();
m_vars.reset();
m_model = nullptr;
m_free_vars.reset();
m_fa.clear();
m_ex.clear();
}
void reset() override {
clear();
m_fa.init();
m_ex.init();
}
/**
\brief create a quantifier prefix formula.
*/
void hoist(expr_ref& fml) {
if (has_quantified_uninterpreted(m, fml)) {
throw tactic_exception("formula contains uninterpreted functions");
}
proof_ref pr(m);
label_rewriter rw(m);
rw.remove_labels(fml, pr);
quantifier_hoister hoist(m);
app_ref_vector vars(m);
bool is_forall = false;
m_pred_abs.get_free_vars(fml, vars);
m_vars.push_back(vars);
vars.reset();
if (m_mode != qsat_sat) {
is_forall = true;
hoist.pull_quantifier(is_forall, fml, vars);
m_vars.push_back(vars);
filter_vars(vars);
}
else {
hoist.pull_quantifier(is_forall, fml, vars);
m_vars.back().append(vars);
filter_vars(vars);
}
do {
is_forall = !is_forall;
vars.reset();
hoist.pull_quantifier(is_forall, fml, vars);
m_vars.push_back(vars);
filter_vars(vars);
}
while (!vars.empty());
SASSERT(m_vars.back().empty());
initialize_levels();
TRACE("qe", tout << fml << "\n";);
}
void check_sort(sort* s) {
if (m.is_uninterp(s)) {
throw default_exception("qsat does not apply to uninterpreted sorts");
}
}
void filter_vars(app_ref_vector const& vars) {
for (app* v : vars) m_pred_abs.fmc()->hide(v);
for (app* v : vars) check_sort(v->get_sort());
}
void initialize_levels() {
// initialize levels.
for (unsigned i = 0; i < m_vars.size(); ++i) {
max_level lvl;
if (is_exists(i)) {
lvl.m_ex = i;
}
else {
lvl.m_fa = i;
}
for (unsigned j = 0; j < m_vars[i].size(); ++j) {
m_pred_abs.set_expr_level(m_vars[i][j].get(), lvl);
}
}
}
bool validate_defs(char const* msg) {
if (m_model.get() && !m_pred_abs.validate_defs(*m_model.get())) {
TRACE("qe",
tout << msg << "\n";
display(tout);
if (m_level > 0) {
get_kernel(m_level-1).s().display(tout);
}
expr_ref_vector asms(m);
m_pred_abs.get_assumptions(m_model.get(), asms);
tout << asms << "\n";
m_pred_abs.pred2lit(asms);
tout << asms << "\n";);
return false;
}
else {
return true;
}
}
void get_core(expr_ref_vector& core, unsigned level) {
SASSERT(validate_defs("get_core"));
get_kernel(level).get_core(core);
m_pred_abs.pred2lit(core);
}
bool minimize_core(expr_ref_vector& core, unsigned level) {
expr_ref_vector core1(m), core2(m), dels(m);
TRACE("qe", tout << core.size() << "\n";);
mus mus(get_kernel(level).s());
for (expr* arg : core) {
app* a = to_app(arg);
max_level lvl = m_pred_abs.compute_level(a);
if (lvl.max() + 2 <= level) {
VERIFY(core1.size() == mus.add_soft(a));
core1.push_back(a);
}
else {
core2.push_back(a);
mus.add_assumption(a);
}
}
TRACE("qe", tout << core1.size() << " " << core2.size() << "\n";);
if (core1.size() > 8) {
if (l_true != mus.get_mus(core2)) {
return false;
}
TRACE("qe", tout << core1.size() << " -> " << core2.size() << "\n";);
core.reset();
core.append(core2);
}
return true;
}
void check_cancel() {
tactic::checkpoint(m);
}
void display(std::ostream& out) const {
out << "level: " << m_level << "\n";
for (unsigned i = 0; i < m_vars.size(); ++i) {
out << m_vars[i] << "\n";
}
m_pred_abs.display(out);
}
void display(std::ostream& out, model& model) const {
display(out);
model_v2_pp(out, model);
}
void display(std::ostream& out, expr_ref_vector const& asms) const {
m_pred_abs.display(out, asms);
}
void add_assumption(expr* fml) {
expr_ref eq(m);
app_ref b = m_pred_abs.fresh_bool("b");
m_asms.push_back(b);
eq = m.mk_eq(b, fml);
m_ex.assert_expr(eq);
m_fa.assert_expr(eq);
m_pred_abs.add_pred(b, to_app(fml));
max_level lvl;
m_pred_abs.set_expr_level(b, lvl);
}
bool project_qe(expr_ref_vector& core) {
SASSERT(m_level == 1);
expr_ref fml(m);
model& mdl = *m_model.get();
get_core(core, m_level);
SASSERT(validate_core(mdl, core));
get_vars(m_level);
SASSERT(validate_assumptions(mdl, core));
m_mbp(force_elim(), m_avars, mdl, core);
SASSERT(validate_defs("project_qe"));
if (m_mode == qsat_maximize) {
maximize_core(core, mdl);
}
else {
fml = negate_core(core);
add_assumption(fml);
m_answer.push_back(fml);
m_free_vars.append(m_avars);
}
pop(1);
return true;
}
bool project(expr_ref_vector& core) {
get_core(core, m_level);
TRACE("qe", display(tout); display(tout << "core\n", core););
SASSERT(m_level >= 2);
expr_ref fml(m);
expr_ref_vector defs(m), core_save(m);
model& mdl = *m_model.get();
SASSERT(validate_core(mdl, core));
get_vars(m_level-1);
SASSERT(validate_project(mdl, core));
mdl.set_inline();
m_mbp(force_elim(), m_avars, mdl, core);
TRACE("qe", tout << "aux vars: " << m_avars << "\n";);
for (app* v : m_avars) m_pred_abs.ensure_expr_level(v, m_level-1);
m_free_vars.append(m_avars);
fml = negate_core(core);
unsigned num_scopes = 0;
max_level level;
m_pred_abs.abstract_atoms(fml, level, defs);
m_ex.assert_expr(mk_and(defs));
m_fa.assert_expr(mk_and(defs));
if (level.max() == UINT_MAX) {
num_scopes = 2*(m_level/2);
}
else if (level.max() + 2 > m_level) {
// fishy - this can happen.
TRACE("qe", tout << "max-level: " << level.max() << " level: " << m_level << "\n");
return false;
}
else {
SASSERT(level.max() + 2 <= m_level);
num_scopes = m_level - level.max();
SASSERT(num_scopes >= 2);
if ((num_scopes % 2) != 0)
--num_scopes;
}
pop(num_scopes);
TRACE("qe", tout << "backtrack: " << num_scopes << " new level: " << m_level << "\nproject:\n" << core << "\n|->\n" << fml << "\n";);
if (m_level == 0 && m_mode != qsat_sat) {
add_assumption(fml);
}
else {
fml = m_pred_abs.mk_abstract(fml);
TRACE("qe_block", tout << "Blocking fml at level: " << m_level << "\n" << fml << "\n";);
get_kernel(m_level).assert_blocking_fml(fml);
}
SASSERT(!m_model.get());
return true;
}
void get_vars(unsigned level) {
m_avars.reset();
for (unsigned i = level; i < m_vars.size(); ++i) {
m_avars.append(m_vars[i]);
}
}
expr_ref negate_core(expr_ref_vector const& core) {
return ::push_not(::mk_and(core));
}
bool force_elim() const {
return m_mode != qsat_qe_rec;
}
expr_ref elim_rec(expr* fml) {
expr_ref tmp(m);
expr_ref_vector trail(m);
obj_map visited;
ptr_vector todo;
trail.push_back(fml);
todo.push_back(fml);
expr* e = nullptr, *r = nullptr;
while (!todo.empty()) {
check_cancel();
e = todo.back();
if (visited.contains(e)) {
todo.pop_back();
continue;
}
switch(e->get_kind()) {
case AST_APP: {
app* a = to_app(e);
expr_ref_vector args(m);
bool all_visited = true;
for (expr* arg : *a) {
if (visited.find(arg, r)) {
args.push_back(r);
}
else {
todo.push_back(arg);
all_visited = false;
}
}
if (all_visited) {
r = m.mk_app(a->get_decl(), args);
todo.pop_back();
trail.push_back(r);
visited.insert(e, r);
}
break;
}
case AST_QUANTIFIER: {
if (is_lambda(e)) {
visited.insert(e, e);
todo.pop_back();
break;
}
SASSERT(!is_lambda(e));
app_ref_vector vars(m);
quantifier* q = to_quantifier(e);
bool is_fa = ::is_forall(q);
tmp = q->get_expr();
extract_vars(q, tmp, vars);
TRACE("qe", tout << vars << " " << mk_pp(q, m) << " " << tmp << "\n";);
tmp = elim_rec(tmp);
if (is_fa) {
tmp = ::push_not(tmp);
}
TRACE("qe", tout << "elim-rec " << tmp << "\n";);
tmp = elim(vars, tmp);
if (!tmp) {
visited.insert(e, e);
todo.pop_back();
break;
}
if (is_fa) {
tmp = ::push_not(tmp);
}
trail.push_back(tmp);
TRACE("qe", tout << tmp << "\n";);
visited.insert(e, tmp);
todo.pop_back();
break;
}
default:
UNREACHABLE();
break;
}
}
VERIFY (visited.find(fml, e));
return expr_ref(e, m);
}
/*
* Solve ex (x) phi(x)
*/
expr_ref elim(app_ref_vector& vars, expr* _fml) {
expr_ref fml(_fml, m);
TRACE("qe", tout << vars << ": " << fml << "\n";);
expr_ref_vector defs(m);
if (has_quantifiers(fml)) {
return expr_ref(m);
}
reset();
fml = ::mk_exists(m, vars.size(), vars.data(), fml);
fml = ::push_not(fml);
hoist(fml);
if (!is_ground(fml)) {
throw tactic_exception("formula is not hoistable");
}
m_pred_abs.abstract_atoms(fml, defs);
fml = m_pred_abs.mk_abstract(fml);
m_ex.assert_expr(mk_and(defs));
m_fa.assert_expr(mk_and(defs));
m_ex.assert_expr(fml);
m_fa.assert_expr(m.mk_not(fml));
TRACE("qe", tout << "ex: " << fml << "\n";);
lbool is_sat = check_sat();
unsigned j = 0;
switch (is_sat) {
case l_false:
fml = ::mk_and(m_answer);
for (app* v : m_free_vars) {
if (occurs(v, fml)) m_free_vars[j++] = v;
}
m_free_vars.shrink(j);
if (!m_free_vars.empty()) {
fml = ::mk_exists(m, m_free_vars.size(), m_free_vars.data(), fml);
}
return fml;
default:
return expr_ref(m);
}
}
bool validate_assumptions(model& mdl, expr_ref_vector const& core) {
for (expr* c : core) {
if (!mdl.is_true(c)) {
TRACE("qe", tout << "component of core is not true: " << mk_pp(c, m) << "\n";
tout << mdl << "\n";);
if (mdl.is_false(c)) {
return false;
}
}
}
return true;
}
bool validate_core(model& mdl, expr_ref_vector const& core) {
return true;
#if 0
TRACE("qe", tout << "Validate core\n";);
solver& s = get_kernel(m_level).s();
expr_ref_vector fmls(m);
fmls.append(core.size(), core.c_ptr());
s.get_assertions(fmls);
return check_fmls(fmls) || !m.inc();
#endif
}
bool check_fmls(expr_ref_vector const& fmls) {
smt_params p;
smt::kernel solver(m, p);
for (unsigned i = 0; i < fmls.size(); ++i) {
solver.assert_expr(fmls[i]);
}
lbool is_sat = solver.check();
CTRACE("qe", is_sat != l_false,
tout << fmls << "\nare not unsat\n";);
return (is_sat == l_false) || !m.inc();
}
bool validate_model(expr_ref_vector const& asms) {
return true;
#if 0
TRACE("qe", tout << "Validate model\n";);
solver& s = get_kernel(m_level).s();
expr_ref_vector fmls(m);
s.get_assertions(fmls);
return
validate_model(*m_model, asms.size(), asms.c_ptr()) &&
validate_model(*m_model, fmls.size(), fmls.c_ptr());
#endif
}
bool validate_model(model& mdl, unsigned sz, expr* const* fmls) {
expr_ref val(m);
for (unsigned i = 0; i < sz; ++i) {
if (!m_model->is_true(fmls[i]) && m.inc()) {
TRACE("qe", tout << "Formula does not evaluate to true in model: " << mk_pp(fmls[i], m) << "\n";);
return false;
}
}
return true;
}
// validate the following:
// proj is true in model.
// core is true in model.
// proj does not contain vars.
// proj => exists vars . core
// (core[model(vars)/vars] => proj)
bool validate_project(model& mdl, expr_ref_vector const& core) {
return true;
#if 0
TRACE("qe", tout << "Validate projection\n";);
if (!validate_model(mdl, core.size(), core.c_ptr())) return false;
expr_ref_vector proj(core);
app_ref_vector vars(m_avars);
m_mbp(false, vars, mdl, proj);
if (!vars.empty()) {
TRACE("qe", tout << "Not validating partial projection\n";);
return true;
}
if (!validate_model(mdl, proj.size(), proj.c_ptr())) {
TRACE("qe", tout << "Projection is false in model\n";);
return false;
}
if (!m.inc()) {
return true;
}
for (unsigned i = 0; i < m_avars.size(); ++i) {
contains_app cont(m, m_avars.get(i));
if (cont(proj)) {
TRACE("qe", tout << "Projection contains free variable: " << mk_pp(m_avars.get(i), m) << "\n";);
return false;
}
}
//
// TBD: proj => exists vars . core,
// e.g., extract and use realizer functions from mbp.
//
// (core[model(vars)/vars] => proj)
expr_ref_vector fmls(m);
fmls.append(core.size(), core.c_ptr());
for (expr* v : m_avars) {
expr_ref val(m);
VERIFY(mdl.eval(v, val));
fmls.push_back(m.mk_eq(v, val));
}
fmls.push_back(m.mk_not(mk_and(proj)));
if (!check_fmls(fmls)) {
TRACE("qe", tout << "implication check failed, could be due to turning != into >\n";);
}
return true;
#endif
}
public:
qsat(ast_manager& m, params_ref const& p, qsat_mode mode):
m(m),
m_mbp(m),
m_fa(m),
m_ex(m),
m_pred_abs(m),
m_answer(m),
m_asms(m),
m_level(0),
m_mode(mode),
m_avars(m),
m_free_vars(m),
m_objective(nullptr),
m_value(nullptr),
m_was_sat(false),
m_gt(m)
{
params_ref q = params_ref();
q.set_bool("use_qel", false);
m_mbp.updt_params(q);
}
~qsat() override {
clear();
}
char const* name() const override { return "qsat"; }
void updt_params(params_ref const & p) override {
}
void collect_param_descrs(param_descrs & r) override {
}
void operator()(/* in */ goal_ref const & in,
/* out */ goal_ref_buffer & result) override {
tactic_report report("qsat-tactic", *in);
model_evaluator_params mp(m_params);
if (!mp.array_equalities())
throw tactic_exception("array equalities cannot be disabled for qsat");
ptr_vector fmls;
expr_ref_vector defs(m);
expr_ref fml(m);
in->get_formulas(fmls);
fml = mk_and(m, fmls.size(), fmls.data());
// for now:
// fail if cores. (TBD)
// fail if proofs. (TBD)
TRACE("qe", tout << fml << "\n";);
if (m_mode == qsat_qe_rec) {
fml = elim_rec(fml);
in->reset();
in->inc_depth();
in->assert_expr(fml);
result.push_back(in.get());
return;
}
reset();
if (m_mode != qsat_sat) {
fml = push_not(fml);
}
hoist(fml);
if (!is_ground(fml)) {
throw tactic_exception("formula is not hoistable");
}
m_pred_abs.abstract_atoms(fml, defs);
fml = m_pred_abs.mk_abstract(fml);
m_ex.assert_expr(mk_and(defs));
m_fa.assert_expr(mk_and(defs));
m_ex.assert_expr(fml);
m_fa.assert_expr(m.mk_not(fml));
TRACE("qe", tout << "ex: " << fml << "\n";);
lbool is_sat = check_sat();
switch (is_sat) {
case l_false:
in->reset();
in->inc_depth();
if (m_mode == qsat_qe) {
fml = ::mk_and(m_answer);
in->assert_expr(fml);
}
else {
SASSERT(m_mode == qsat_sat);
in->assert_expr(m.mk_false());
}
result.push_back(in.get());
break;
case l_true:
in->reset();
in->inc_depth();
result.push_back(in.get());
if (in->models_enabled()) {
model_converter_ref mc;
mc = model2model_converter(m_model_save.get());
mc = concat(m_pred_abs.fmc(), mc.get());
in->add(mc.get());
}
break;
case l_undef:
result.push_back(in.get());
std::string s = m_ex.s().reason_unknown();
if (s == "ok" || s == "unknown") {
s = m_fa.s().reason_unknown();
}
throw tactic_exception(std::move(s));
}
}
void collect_statistics(statistics & st) const override {
st.copy(m_st);
m_fa.collect_statistics(st);
m_ex.collect_statistics(st);
m_pred_abs.collect_statistics(st);
st.update("qsat num rounds", m_stats.m_num_rounds);
m_pred_abs.collect_statistics(st);
}
void reset_statistics() override {
m_stats.reset();
m_fa.reset_statistics();
m_ex.reset_statistics();
}
void cleanup() override {
clear();
}
void set_logic(symbol const & l) override {
}
void set_progress_callback(progress_callback * callback) override {
}
tactic * translate(ast_manager & m) override {
return alloc(qsat, m, m_params, m_mode);
}
lbool maximize(expr_ref_vector const& fmls, app* t, model_ref& mdl, opt::inf_eps& value) {
expr_ref_vector defs(m);
expr_ref fml = mk_and(fmls);
hoist(fml);
m_objective = t;
m_value = &value;
m_was_sat = false;
m_model_save.reset();
m_pred_abs.abstract_atoms(fml, defs);
fml = m_pred_abs.mk_abstract(fml);
m_ex.assert_expr(mk_and(defs));
m_fa.assert_expr(mk_and(defs));
m_ex.assert_expr(fml);
m_fa.assert_expr(m.mk_not(fml));
lbool is_sat = check_sat();
mdl = m_model.get();
switch (is_sat) {
case l_false:
if (!m_was_sat) {
return l_false;
}
mdl = m_model_save;
break;
case l_true:
UNREACHABLE();
break;
case l_undef:
std::string s = m_ex.s().reason_unknown();
if (s == "ok") {
s = m_fa.s().reason_unknown();
}
throw tactic_exception(std::move(s));
}
return l_true;
}
void maximize_core(expr_ref_vector const& core, model& mdl) {
SASSERT(m_value);
SASSERT(m_objective);
TRACE("qe", tout << "maximize: " << core << "\n";);
m_was_sat |= !core.empty();
expr_ref bound(m);
*m_value = m_value_save;
IF_VERBOSE(3, verbose_stream() << "(maximize " << *m_value << ")\n";);
m_ex.assert_expr(m_gt);
m_fa.assert_expr(m_gt);
}
void maximize_model() {
SASSERT(m_level == 1 && m_mode == qsat_maximize);
SASSERT(m_objective);
expr_ref ge(m);
expr_ref_vector asms(m), defs(m);
m_pred_abs.get_assumptions(m_model.get(), asms);
m_pred_abs.pred2lit(asms);
SASSERT(validate_defs("maximize_model1"));
m_value_save = m_mbp.maximize(asms, *m_model.get(), m_objective, ge, m_gt);
SASSERT(validate_defs("maximize_model2"));
// bound := val <= m_objective
IF_VERBOSE(3, verbose_stream() << "(qsat-maximize-bound: " << m_value_save << ")\n";);
max_level level;
m_pred_abs.abstract_atoms(ge, level, defs);
m_ex.assert_expr(mk_and(defs));
m_fa.assert_expr(mk_and(defs));
ge = m_pred_abs.mk_abstract(ge);
SASSERT(is_uninterp_const(ge));
// update model with evaluation for bound.
if (is_uninterp_const(ge)) {
m_model->register_decl(to_app(ge)->get_decl(), m.mk_true());
}
SASSERT(validate_defs("maximize_model3"));
}
};
lbool maximize(expr_ref_vector const& fmls, app* t, opt::inf_eps& value, model_ref& mdl, params_ref const& p) {
ast_manager& m = fmls.get_manager();
qsat qs(m, p, qsat_maximize);
return qs.maximize(fmls, t, mdl, value);
}
struct qmax::imp {
qsat m_qsat;
imp(ast_manager& m, params_ref const& p):
m_qsat(m, p, qsat_maximize)
{}
};
qmax::qmax(ast_manager& m, params_ref const& p) {
m_imp = alloc(imp, m, p);
}
qmax::~qmax() {
dealloc(m_imp);
}
lbool qmax::operator()(expr_ref_vector const& fmls, app* t, opt::inf_eps& value, model_ref& mdl) {
return m_imp->m_qsat.maximize(fmls, t, mdl, value);
}
void qmax::collect_statistics(statistics& st) const {
m_imp->m_qsat.collect_statistics(st);
}
};
tactic * mk_qsat_tactic(ast_manager& m, params_ref const& p) {
return alloc(qe::qsat, m, p, qe::qsat_sat);
}
tactic * mk_qe2_tactic(ast_manager& m, params_ref const& p) {
return alloc(qe::qsat, m, p, qe::qsat_qe);
}
tactic * mk_qe_rec_tactic(ast_manager& m, params_ref const& p) {
return alloc(qe::qsat, m, p, qe::qsat_qe_rec);
}