3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-05-02 13:27:01 +00:00

adding dt-solver (#4739)

* adding dt-solver

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* dt

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* move mbp to self-contained module

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* files

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* Create CMakeLists.txt

* dt

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* rename to bool_var2expr to indicate type class

* mbp

* na
This commit is contained in:
Nikolaj Bjorner 2020-10-18 15:28:21 -07:00 committed by GitHub
parent b77c57451f
commit 2f756da294
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 2309 additions and 1257 deletions

12
src/qe/mbp/CMakeLists.txt Normal file
View file

@ -0,0 +1,12 @@
z3_add_component(mbp
SOURCES
mbp_arith.cpp
mbp_arrays.cpp
mbp_datatypes.cpp
mbp_plugin.cpp
mbp_solve_plugin.cpp
mbp_term_graph.cpp
COMPONENT_DEPENDENCIES
model
simplex
)

573
src/qe/mbp/mbp_arith.cpp Normal file
View file

@ -0,0 +1,573 @@
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
qe_arith.cpp
Abstract:
Simple projection function for real arithmetic based on Loos-W.
Author:
Nikolaj Bjorner (nbjorner) 2013-09-12
Revision History:
Moved projection functionality to model_based_opt module. 2016-06-26
--*/
#include "qe/mbp/mbp_arith.h"
#include "ast/ast_util.h"
#include "ast/arith_decl_plugin.h"
#include "ast/ast_pp.h"
#include "ast/rewriter/th_rewriter.h"
#include "ast/expr_functors.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "math/simplex/model_based_opt.h"
#include "model/model_evaluator.h"
#include "model/model_smt2_pp.h"
#include "model/model_v2_pp.h"
namespace mbp {
struct arith_project_plugin::imp {
ast_manager& m;
arith_util a;
bool m_check_purified; // check that variables are properly pure
void insert_mul(expr* x, rational const& v, obj_map<expr, rational>& ts) {
// TRACE("qe", tout << "Adding variable " << mk_pp(x, m) << " " << v << "\n";);
rational w;
if (ts.find(x, w))
ts.insert(x, w + v);
else
ts.insert(x, v);
}
//
// extract linear inequalities from literal 'lit' into the model-based optimization manager 'mbo'.
// It uses the current model to choose values for conditionals and it primes mbo with the current
// interpretation of sub-expressions that are treated as variables for mbo.
//
bool linearize(opt::model_based_opt& mbo, model_evaluator& eval, expr* lit, expr_ref_vector& fmls, obj_map<expr, unsigned>& tids) {
obj_map<expr, rational> ts;
rational c(0), mul(1);
expr_ref t(m);
opt::ineq_type ty = opt::t_le;
expr* e1, *e2;
DEBUG_CODE(expr_ref val(m);
eval(lit, val);
CTRACE("qe", !m.is_true(val), tout << mk_pp(lit, m) << " := " << val << "\n";);
SASSERT(m.limit().is_canceled() || !m.is_false(val)););
if (!m.inc())
return false;
TRACE("opt", tout << mk_pp(lit, m) << " " << a.is_lt(lit) << " " << a.is_gt(lit) << "\n";);
bool is_not = m.is_not(lit, lit);
if (is_not) {
mul.neg();
}
SASSERT(!m.is_not(lit));
if ((a.is_le(lit, e1, e2) || a.is_ge(lit, e2, e1))) {
linearize(mbo, eval, mul, e1, c, fmls, ts, tids);
linearize(mbo, eval, -mul, e2, c, fmls, ts, tids);
ty = is_not ? opt::t_lt : opt::t_le;
}
else if ((a.is_lt(lit, e1, e2) || a.is_gt(lit, e2, e1))) {
linearize(mbo, eval, mul, e1, c, fmls, ts, tids);
linearize(mbo, eval, -mul, e2, c, fmls, ts, tids);
ty = is_not ? opt::t_le: opt::t_lt;
}
else if (m.is_eq(lit, e1, e2) && !is_not && is_arith(e1)) {
linearize(mbo, eval, mul, e1, c, fmls, ts, tids);
linearize(mbo, eval, -mul, e2, c, fmls, ts, tids);
ty = opt::t_eq;
}
else if (m.is_eq(lit, e1, e2) && is_not && is_arith(e1)) {
rational r1, r2;
expr_ref val1 = eval(e1);
expr_ref val2 = eval(e2);
//TRACE("qe", tout << mk_pp(e1, m) << " " << val1 << "\n";);
//TRACE("qe", tout << mk_pp(e2, m) << " " << val2 << "\n";);
if (!a.is_numeral(val1, r1)) return false;
if (!a.is_numeral(val2, r2)) return false;
SASSERT(r1 != r2);
if (r1 < r2) {
std::swap(e1, e2);
}
ty = opt::t_lt;
linearize(mbo, eval, mul, e1, c, fmls, ts, tids);
linearize(mbo, eval, -mul, e2, c, fmls, ts, tids);
}
else if (m.is_distinct(lit) && !is_not && is_arith(to_app(lit)->get_arg(0))) {
expr_ref val(m);
rational r;
app* alit = to_app(lit);
vector<std::pair<expr*,rational> > nums;
for (expr* arg : *alit) {
val = eval(arg);
TRACE("qe", tout << mk_pp(arg, m) << " " << val << "\n";);
if (!a.is_numeral(val, r)) return false;
nums.push_back(std::make_pair(arg, r));
}
std::sort(nums.begin(), nums.end(), compare_second());
for (unsigned i = 0; i + 1 < nums.size(); ++i) {
SASSERT(nums[i].second < nums[i+1].second);
expr_ref fml(a.mk_lt(nums[i].first, nums[i+1].first), m);
if (!linearize(mbo, eval, fml, fmls, tids)) {
return false;
}
}
return true;
}
else if (m.is_distinct(lit) && is_not && is_arith(to_app(lit)->get_arg(0))) {
// find the two arguments that are equal.
// linearize these.
map<rational, expr*, rational::hash_proc, rational::eq_proc> values;
bool found_eq = false;
for (unsigned i = 0; !found_eq && i < to_app(lit)->get_num_args(); ++i) {
expr* arg1 = to_app(lit)->get_arg(i), *arg2 = nullptr;
rational r;
expr_ref val = eval(arg1);
TRACE("qe", tout << mk_pp(arg1, m) << " " << val << "\n";);
if (!a.is_numeral(val, r)) return false;
if (values.find(r, arg2)) {
ty = opt::t_eq;
linearize(mbo, eval, mul, arg1, c, fmls, ts, tids);
linearize(mbo, eval, -mul, arg2, c, fmls, ts, tids);
found_eq = true;
}
else {
values.insert(r, arg1);
}
}
SASSERT(found_eq);
}
else {
TRACE("qe", tout << "Skipping " << mk_pp(lit, m) << "\n";);
return false;
}
vars coeffs;
extract_coefficients(mbo, eval, ts, tids, coeffs);
mbo.add_constraint(coeffs, c, ty);
return true;
}
//
// convert linear arithmetic term into an inequality for mbo.
//
void linearize(opt::model_based_opt& mbo, model_evaluator& eval, rational const& mul, expr* t, rational& c,
expr_ref_vector& fmls, obj_map<expr, rational>& ts, obj_map<expr, unsigned>& tids) {
expr* t1, *t2, *t3;
rational mul1;
expr_ref val(m);
if (a.is_mul(t, t1, t2) && is_numeral(t1, mul1)) {
linearize(mbo, eval, mul* mul1, t2, c, fmls, ts, tids);
}
else if (a.is_mul(t, t1, t2) && is_numeral(t2, mul1)) {
linearize(mbo, eval, mul* mul1, t1, c, fmls, ts, tids);
}
else if (a.is_add(t)) {
app* ap = to_app(t);
for (expr* arg : *ap) {
linearize(mbo, eval, mul, arg, c, fmls, ts, tids);
}
}
else if (a.is_sub(t, t1, t2)) {
linearize(mbo, eval, mul, t1, c, fmls, ts, tids);
linearize(mbo, eval, -mul, t2, c, fmls, ts, tids);
}
else if (a.is_uminus(t, t1)) {
linearize(mbo, eval, -mul, t1, c, fmls, ts, tids);
}
else if (a.is_numeral(t, mul1)) {
c += mul*mul1;
}
else if (m.is_ite(t, t1, t2, t3)) {
val = eval(t1);
SASSERT(m.is_true(val) || m.is_false(val));
TRACE("qe", tout << mk_pp(t1, m) << " := " << val << "\n";);
if (m.is_true(val)) {
linearize(mbo, eval, mul, t2, c, fmls, ts, tids);
fmls.push_back(t1);
}
else {
expr_ref not_t1(mk_not(m, t1), m);
fmls.push_back(not_t1);
linearize(mbo, eval, mul, t3, c, fmls, ts, tids);
}
}
else if (a.is_mod(t, t1, t2) && is_numeral(t2, mul1) && !mul1.is_zero()) {
rational r;
val = eval(t);
if (!a.is_numeral(val, r)) {
throw default_exception("mbp evaluation didn't produce an integer");
}
c += mul*r;
// t1 mod mul1 == r
rational c0(-r), mul0(1);
obj_map<expr, rational> ts0;
linearize(mbo, eval, mul0, t1, c0, fmls, ts0, tids);
vars coeffs;
extract_coefficients(mbo, eval, ts0, tids, coeffs);
mbo.add_divides(coeffs, c0, mul1);
}
else {
insert_mul(t, mul, ts);
}
}
bool is_numeral(expr* t, rational& r) {
return a.is_extended_numeral(t, r);
}
struct compare_second {
bool operator()(std::pair<expr*, rational> const& a,
std::pair<expr*, rational> const& b) const {
return a.second < b.second;
}
};
bool is_arith(expr* e) {
return a.is_int_real(e);
}
rational n_sign(rational const& b) {
return rational(b.is_pos()?-1:1);
}
imp(ast_manager& m):
m(m), a(m), m_check_purified(true) {}
~imp() {}
bool operator()(model& model, app* v, app_ref_vector& vars, expr_ref_vector& lits) {
app_ref_vector vs(m);
vs.push_back(v);
project(model, vs, lits, false);
return vs.empty();
}
typedef opt::model_based_opt::var var;
typedef opt::model_based_opt::row row;
typedef vector<var> vars;
expr_ref var2expr(ptr_vector<expr> const& index2expr, var const& v) {
expr_ref t(index2expr[v.m_id], m);
if (!v.m_coeff.is_one()) {
t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t);
}
return t;
}
vector<def> project(model& model, app_ref_vector& vars, expr_ref_vector& fmls, bool compute_def) {
bool has_arith = false;
for (expr* v : vars) {
has_arith |= is_arith(v);
}
if (!has_arith)
return vector<def>();
model_evaluator eval(model);
TRACE("qe", tout << model;);
eval.set_model_completion(true);
opt::model_based_opt mbo;
obj_map<expr, unsigned> tids;
expr_ref_vector pinned(m);
unsigned j = 0;
TRACE("qe", tout << "vars: " << vars << "\nfmls: " << fmls << "\n";);
for (unsigned i = 0; i < fmls.size(); ++i) {
expr * fml = fmls.get(i);
if (!linearize(mbo, eval, fml, fmls, tids)) {
TRACE("qe", tout << "could not linearize: " << mk_pp(fml, m) << "\n";);
fmls[j++] = fml;
}
else {
pinned.push_back(fml);
}
}
fmls.shrink(j);
TRACE("qe", tout << "formulas\n" << fmls << "\n";);
// fmls holds residue,
// mbo holds linear inequalities that are in scope
// collect variables in residue an in tids.
// filter variables that are absent from residue.
// project those.
// collect result of projection
// return those to fmls.
expr_mark var_mark, fmls_mark;
for (app * v : vars) {
var_mark.mark(v);
if (is_arith(v) && !tids.contains(v)) {
rational r;
expr_ref val = eval(v);
if (!m.inc())
return vector<def>();
if (!a.is_numeral(val, r))
throw default_exception("evaluation did not produce a numeral");
TRACE("qe", tout << mk_pp(v, m) << " " << val << "\n";);
tids.insert(v, mbo.add_var(r, a.is_int(v)));
}
}
if (m_check_purified) {
for (expr* fml : fmls)
mark_rec(fmls_mark, fml);
for (auto& kv : tids) {
expr* e = kv.m_key;
if (!var_mark.is_marked(e)) {
mark_rec(fmls_mark, e);
}
}
}
ptr_vector<expr> index2expr;
for (auto& kv : tids)
index2expr.setx(kv.m_value, kv.m_key, nullptr);
j = 0;
unsigned_vector real_vars;
for (app* v : vars) {
if (is_arith(v) && !fmls_mark.is_marked(v))
real_vars.push_back(tids.find(v));
else
vars[j++] = v;
}
vars.shrink(j);
TRACE("qe", tout << "remaining vars: " << vars << "\n";
for (unsigned v : real_vars) tout << "v" << v << " " << mk_pp(index2expr[v], m) << "\n";
mbo.display(tout););
vector<opt::model_based_opt::def> defs = mbo.project(real_vars.size(), real_vars.c_ptr(), compute_def);
TRACE("qe", mbo.display(tout << "mbo result\n");
for (auto const& d : defs) tout << "def: " << d << "\n";);
vector<row> rows;
mbo.get_live_rows(rows);
rows2fmls(rows, index2expr, fmls);
vector<def> result;
if (compute_def)
optdefs2mbpdef(defs, index2expr, real_vars, result);
return result;
}
void optdefs2mbpdef(vector<opt::model_based_opt::def> const& defs, ptr_vector<expr> const& index2expr, unsigned_vector const& real_vars, vector<def>& result) {
SASSERT(defs.size() == real_vars.size());
for (unsigned i = 0; i < defs.size(); ++i) {
auto const& d = defs[i];
expr* x = index2expr[real_vars[i]];
bool is_int = a.is_int(x);
expr_ref_vector ts(m);
expr_ref t(m);
for (var const& v : d.m_vars)
ts.push_back(var2expr(index2expr, v));
if (!d.m_coeff.is_zero())
ts.push_back(a.mk_numeral(d.m_coeff, is_int));
t = mk_add(ts);
if (!d.m_div.is_one() && is_int)
t = a.mk_idiv(t, a.mk_numeral(d.m_div, is_int));
else if (!d.m_div.is_one() && !is_int)
t = a.mk_div(t, a.mk_numeral(d.m_div, is_int));
result.push_back(def(expr_ref(x, m), t));
}
}
void rows2fmls(vector<row> const& rows, ptr_vector<expr> const& index2expr, expr_ref_vector& fmls) {
for (row const& r : rows) {
expr_ref_vector ts(m);
expr_ref t(m), s(m), val(m);
if (r.m_vars.empty()) {
continue;
}
if (r.m_vars.size() == 1 && r.m_vars[0].m_coeff.is_neg() && r.m_type != opt::t_mod) {
var const& v = r.m_vars[0];
t = index2expr[v.m_id];
if (!v.m_coeff.is_minus_one()) {
t = a.mk_mul(a.mk_numeral(-v.m_coeff, a.is_int(t)), t);
}
s = a.mk_numeral(r.m_coeff, a.is_int(t));
switch (r.m_type) {
case opt::t_lt: t = a.mk_gt(t, s); break;
case opt::t_le: t = a.mk_ge(t, s); break;
case opt::t_eq: t = a.mk_eq(t, s); break;
default: UNREACHABLE();
}
fmls.push_back(t);
continue;
}
for (var const& v : r.m_vars) {
t = index2expr[v.m_id];
if (!v.m_coeff.is_one()) {
t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t);
}
ts.push_back(t);
}
t = mk_add(ts);
s = a.mk_numeral(-r.m_coeff, r.m_coeff.is_int() && a.is_int(t));
switch (r.m_type) {
case opt::t_lt: t = a.mk_lt(t, s); break;
case opt::t_le: t = a.mk_le(t, s); break;
case opt::t_eq: t = a.mk_eq(t, s); break;
case opt::t_mod:
if (!r.m_coeff.is_zero()) {
t = a.mk_sub(t, s);
}
t = a.mk_eq(a.mk_mod(t, a.mk_numeral(r.m_mod, true)), a.mk_int(0));
break;
}
fmls.push_back(t);
}
}
expr_ref mk_add(expr_ref_vector const& ts) {
return a.mk_add_simplify(ts);
}
opt::inf_eps maximize(expr_ref_vector const& fmls0, model& mdl, app* t, expr_ref& ge, expr_ref& gt) {
SASSERT(a.is_real(t));
expr_ref_vector fmls(fmls0);
opt::model_based_opt mbo;
opt::inf_eps value;
obj_map<expr, rational> ts;
obj_map<expr, unsigned> tids;
model_evaluator eval(mdl);
// extract objective function.
vars coeffs;
rational c(0), mul(1);
linearize(mbo, eval, mul, t, c, fmls, ts, tids);
extract_coefficients(mbo, eval, ts, tids, coeffs);
mbo.set_objective(coeffs, c);
SASSERT(validate_model(eval, fmls0));
// extract linear constraints
for (expr * fml : fmls) {
linearize(mbo, eval, fml, fmls, tids);
}
// find optimal value
value = mbo.maximize();
// update model to use new values that satisfy optimality
ptr_vector<expr> vars;
for (auto& kv : tids) {
expr* e = kv.m_key;
if (is_uninterp_const(e)) {
unsigned id = kv.m_value;
func_decl* f = to_app(e)->get_decl();
expr_ref val(a.mk_numeral(mbo.get_value(id), false), m);
mdl.register_decl(f, val);
}
else {
TRACE("qe", tout << "omitting model update for non-uninterpreted constant " << mk_pp(e, m) << "\n";);
}
}
expr_ref val(a.mk_numeral(value.get_rational(), false), m);
expr_ref tval = eval(t);
// update the predicate 'bound' which forces larger values when 'strict' is true.
// strict: bound := valuue < t
// !strict: bound := value <= t
if (!value.is_finite()) {
ge = a.mk_ge(t, tval);
gt = m.mk_false();
}
else if (value.get_infinitesimal().is_neg()) {
ge = a.mk_ge(t, tval);
gt = a.mk_ge(t, val);
}
else {
ge = a.mk_ge(t, val);
gt = a.mk_gt(t, val);
}
SASSERT(validate_model(eval, fmls0));
return value;
}
bool validate_model(model_evaluator& eval, expr_ref_vector const& fmls) {
bool valid = true;
for (expr* fml : fmls) {
expr_ref val = eval(fml);
if (!m.is_true(val)) {
valid = false;
TRACE("qe", tout << mk_pp(fml, m) << " := " << val << "\n";);
}
}
return valid;
}
void extract_coefficients(opt::model_based_opt& mbo, model_evaluator& eval, obj_map<expr, rational> const& ts, obj_map<expr, unsigned>& tids, vars& coeffs) {
coeffs.reset();
eval.set_model_completion(true);
for (auto& kv : ts) {
unsigned id;
expr* v = kv.m_key;
if (!tids.find(v, id)) {
rational r;
expr_ref val = eval(v);
if (!a.is_numeral(val, r)) {
TRACE("qe", tout << eval.get_model() << "\n";);
throw default_exception("mbp evaluation was only partial");
}
id = mbo.add_var(r, a.is_int(v));
tids.insert(v, id);
}
CTRACE("qe", kv.m_value.is_zero(), tout << mk_pp(v, m) << " has coefficeint 0\n";);
if (!kv.m_value.is_zero()) {
coeffs.push_back(var(id, kv.m_value));
}
}
}
};
arith_project_plugin::arith_project_plugin(ast_manager& m):project_plugin(m) {
m_imp = alloc(imp, m);
}
arith_project_plugin::~arith_project_plugin() {
dealloc(m_imp);
}
bool arith_project_plugin::operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) {
return (*m_imp)(model, var, vars, lits);
}
void arith_project_plugin::operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
m_imp->project(model, vars, lits, false);
}
vector<def> arith_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
return m_imp->project(model, vars, lits, true);
}
void arith_project_plugin::set_check_purified(bool check_purified) {
m_imp->m_check_purified = check_purified;
}
family_id arith_project_plugin::get_family_id() {
return m_imp->a.get_family_id();
}
opt::inf_eps arith_project_plugin::maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt) {
return m_imp->maximize(fmls, mdl, t, ge, gt);
}
bool arith_project(model& model, app* var, expr_ref_vector& lits) {
ast_manager& m = lits.get_manager();
arith_project_plugin ap(m);
app_ref_vector vars(m);
return ap(model, var, vars, lits);
}
}

50
src/qe/mbp/mbp_arith.h Normal file
View file

@ -0,0 +1,50 @@
/*++
Copyright (c) 2015 Microsoft Corporation
--*/
#pragma once
#include "ast/arith_decl_plugin.h"
#include "model/model.h"
#include "qe/mbp/mbp_plugin.h"
namespace mbp {
/**
Loos-Weispfenning model-based projection for a basic conjunction.
Lits is a vector of literals.
return vector of variables that could not be projected.
*/
class arith_project_plugin : public project_plugin {
struct imp;
imp* m_imp;
public:
arith_project_plugin(ast_manager& m);
~arith_project_plugin() override;
bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override;
bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override { return false; }
family_id get_family_id() override;
void operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) override;
vector<def> project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override;
void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) override { UNREACHABLE(); }
opt::inf_eps maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt);
/**
* \brief check if formulas are purified, or leave it to caller to ensure that
* arithmetic variables nested under foreign functions are handled properly.
*/
void set_check_purified(bool check_purified);
};
bool arith_project(model& model, app* var, expr_ref_vector& lits);
};

1636
src/qe/mbp/mbp_arrays.cpp Normal file

File diff suppressed because it is too large Load diff

44
src/qe/mbp/mbp_arrays.h Normal file
View file

@ -0,0 +1,44 @@
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
mbp_arrays.h
Abstract:
Model based projection for arrays
Author:
Nikolaj Bjorner (nbjorner) 2015-06-13
Revision History:
--*/
#pragma once
#include "ast/array_decl_plugin.h"
#include "qe/mbp/mbp_plugin.h"
namespace mbp {
class array_project_plugin : public project_plugin {
struct imp;
imp* m_imp;
public:
array_project_plugin(ast_manager& m);
~array_project_plugin() override;
bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override;
bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override;
void operator()(model& model, app_ref_vector& vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects);
family_id get_family_id() override;
vector<def> project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override;
void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) override;
};
};

View file

@ -0,0 +1,320 @@
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
qe_datatypes.cpp
Abstract:
Simple projection function for algebraic datatypes.
Author:
Nikolaj Bjorner (nbjorner) 2015-06-13
Revision History:
--*/
#include "ast/ast_pp.h"
#include "ast/rewriter/th_rewriter.h"
#include "ast/expr_functors.h"
#include "model/model_v2_pp.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "util/obj_pair_hashtable.h"
#include "qe/mbp/mbp_datatypes.h"
namespace mbp {
struct datatype_project_plugin::imp {
ast_manager& m;
datatype_util dt;
app_ref m_val;
scoped_ptr<contains_app> m_var;
imp(ast_manager& m):
m(m), dt(m), m_val(m) {}
bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
return lift_foreign(vars, lits);
}
bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) {
expr_ref val = model(var);
SASSERT(is_app(val));
TRACE("qe", tout << mk_pp(var, m) << " := " << val << "\n";);
m_val = to_app(val);
if (!dt.is_constructor(m_val)) {
// assert: var does not occur in lits.
return true;
}
m_var = alloc(contains_app, m, var);
try {
if (dt.is_recursive(m.get_sort(var))) {
project_rec(model, vars, lits);
}
else {
project_nonrec(model, vars, lits);
}
}
catch (const cant_project &) {
TRACE("qe", tout << "can't project:" << mk_pp(var, m) << "\n";);
return false;
}
return true;
}
void project_nonrec(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
expr_ref tmp(m), val(m);
expr_ref_vector args(m);
app_ref arg(m);
SASSERT(dt.is_constructor(m_val));
func_decl* f = m_val->get_decl();
ptr_vector<func_decl> const& acc = *dt.get_constructor_accessors(f);
for (unsigned i = 0; i < acc.size(); ++i) {
auto str = acc[i]->get_name().str();
arg = m.mk_fresh_const(str.c_str(), acc[i]->get_range());
vars.push_back(arg);
model.register_decl(arg->get_decl(), m_val->get_arg(i));
args.push_back(arg);
}
val = m.mk_app(f, args.size(), args.c_ptr());
TRACE("qe", tout << mk_pp(m_var->x(), m) << " |-> " << val << "\n";);
reduce(val, lits);
}
void project_rec(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
expr_ref rhs(m);
expr_ref_vector eqs(m);
for (unsigned i = 0; i < lits.size(); ++i) {
if (solve(model, vars, lits[i].get(), rhs, eqs)) {
project_plugin::erase(lits, i);
reduce(rhs, lits);
lits.append(eqs);
return;
}
}
// otherwise, unfold the constructor associated with m_var
// once according to the model. In this way, selector-constructor
// redexes are reduced and disequalities are eventually solved
// by virtue of the constructors being different.
project_nonrec(model, vars, lits);
}
void reduce(expr* val, expr_ref_vector& lits) {
expr_safe_replace sub(m);
th_rewriter rw(m);
expr_ref tmp(m);
sub.insert(m_var->x(), val);
TRACE("qe", tout << mk_pp(m_var->x(), m) << " = " << mk_pp(val, m) << "\n";
tout << lits << "\n";);
for (unsigned i = 0; i < lits.size(); ++i) {
sub(lits[i].get(), tmp);
rw(tmp);
lits[i] = tmp;
}
}
bool contains_x(expr* e) {
return (*m_var)(e);
}
bool solve(model& model, app_ref_vector& vars, expr* fml, expr_ref& t, expr_ref_vector& eqs) {
expr* t1, *t2;
if (m.is_eq(fml, t1, t2)) {
if (contains_x(t1) && !contains_x(t2) && is_app(t1)) {
return solve(model, vars, to_app(t1), t2, t, eqs);
}
if (contains_x(t2) && !contains_x(t1) && is_app(t2)) {
return solve(model, vars, to_app(t2), t1, t, eqs);
}
}
if (m.is_not(fml, t1) && m.is_distinct(t1)) {
expr_ref eq = project_plugin::pick_equality(m, model, t1);
return solve(model, vars, eq, t, eqs);
}
return false;
}
bool solve(model& model, app_ref_vector& vars, app* a, expr* b, expr_ref& t, expr_ref_vector& eqs) {
SASSERT(contains_x(a));
SASSERT(!contains_x(b));
if (m_var->x() == a) {
t = b;
return true;
}
if (!dt.is_constructor(a)) {
return false;
}
func_decl* c = a->get_decl();
func_decl_ref rec(dt.get_constructor_is(c), m);
ptr_vector<func_decl> const & acc = *dt.get_constructor_accessors(c);
SASSERT(acc.size() == a->get_num_args());
//
// It suffices to solve just the first available equality.
// The others are determined by the first.
//
for (unsigned i = 0; i < a->get_num_args(); ++i) {
expr* l = a->get_arg(i);
if (is_app(l) && contains_x(l)) {
expr_ref r(m);
r = access(c, i, acc, b);
if (solve(model, vars, to_app(l), r, t, eqs)) {
for (unsigned j = 0; j < c->get_arity(); ++j) {
if (i != j) {
eqs.push_back(m.mk_eq(access(c, j, acc, b), a->get_arg(j)));
}
}
if (!is_app_of(b, c)) {
eqs.push_back(m.mk_app(rec, b));
}
return true;
}
}
}
return false;
}
expr* access(func_decl* c, unsigned i, ptr_vector<func_decl> const& acc, expr* e) {
if (is_app_of(e,c)) {
return to_app(e)->get_arg(i);
}
else {
return m.mk_app(acc[i], e);
}
}
bool lift_foreign(app_ref_vector const& vars, expr_ref_vector& lits) {
bool reduced = false;
expr_mark visited;
expr_mark has_var;
bool inserted = false;
for (unsigned i = 0; i < vars.size(); ++i) {
if (m.is_bool(vars[i])) continue;
if (dt.is_datatype(m.get_sort(vars[i]))) continue;
inserted = true;
has_var.mark(vars[i]);
visited.mark(vars[i]);
}
if (inserted) {
for (unsigned i = 0; i < lits.size(); ++i) {
expr* e = lits[i].get(), *l, *r;
if (m.is_eq(e, l, r) && reduce_eq(visited, has_var, l, r, lits)) {
project_plugin::erase(lits, i);
reduced = true;
}
}
CTRACE("qe", reduced, tout << vars << "\n" << lits << "\n";);
}
return reduced;
}
bool reduce_eq(expr_mark& has_var, expr_mark& visited, expr* l, expr* r, expr_ref_vector& lits) {
if (!is_app(l) || !is_app(r)) {
return false;
}
bool reduce = false;
if (dt.is_constructor(to_app(r)) && contains_foreign(has_var, visited, r)) {
std::swap(l, r);
reduce = true;
}
reduce |= dt.is_constructor(to_app(l)) && contains_foreign(has_var, visited, l);
if (!reduce) {
return false;
}
func_decl* c = to_app(l)->get_decl();
ptr_vector<func_decl> const& acc = *dt.get_constructor_accessors(c);
if (!is_app_of(r, c)) {
lits.push_back(m.mk_app(dt.get_constructor_is(c), r));
}
for (unsigned i = 0; i < acc.size(); ++i) {
lits.push_back(m.mk_eq(to_app(l)->get_arg(i), access(c, i, acc, r)));
}
return true;
}
ptr_vector<expr> todo;
bool contains_foreign(expr_mark& has_var, expr_mark& visited, expr* e) {
todo.push_back(e);
while (!todo.empty()) {
expr* _f = todo.back();
if (visited.is_marked(_f)) {
todo.pop_back();
continue;
}
if (!is_app(_f)) {
visited.mark(_f);
todo.pop_back();
continue;
}
app* f = to_app(_f);
bool has_new = false, has_v = false;
for (unsigned i = 0; i < f->get_num_args(); ++i) {
expr* arg = f->get_arg(i);
if (!visited.is_marked(arg)) {
todo.push_back(arg);
has_new = true;
}
else {
has_v |= has_var.is_marked(arg);
}
}
if (has_new) {
continue;
}
todo.pop_back();
if (has_v) {
has_var.mark(f);
}
TRACE("qe", tout << "contains: " << mk_pp(f, m) << " " << has_var.is_marked(f) << "\n";);
visited.mark(f);
}
TRACE("qe", tout << "contains: " << mk_pp(e, m) << " " << has_var.is_marked(e) << "\n";);
return has_var.is_marked(e);
}
};
datatype_project_plugin::datatype_project_plugin(ast_manager& m):
project_plugin(m) {
m_imp = alloc(imp, m);
}
datatype_project_plugin::~datatype_project_plugin() {
dealloc(m_imp);
}
bool datatype_project_plugin::operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) {
return (*m_imp)(model, var, vars, lits);
}
bool datatype_project_plugin::solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
return m_imp->solve(model, vars, lits);
}
vector<def> datatype_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) {
return vector<def>();
}
void datatype_project_plugin::saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) {
NOT_IMPLEMENTED_YET();
}
family_id datatype_project_plugin::get_family_id() {
return m_imp->dt.get_family_id();
}
}

View file

@ -0,0 +1,43 @@
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
qe_datatypes.h
Abstract:
Model based projection for algebraic datatypes
Author:
Nikolaj Bjorner (nbjorner) 2015-06-13
Revision History:
--*/
#pragma once
#include "ast/datatype_decl_plugin.h"
#include "qe/mbp/mbp_plugin.h"
namespace mbp {
class datatype_project_plugin : public project_plugin {
struct imp;
imp* m_imp;
public:
datatype_project_plugin(ast_manager& m);
~datatype_project_plugin() override;
bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override;
bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override;
family_id get_family_id() override;
vector<def> project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override;
void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) override;
};
};

262
src/qe/mbp/mbp_plugin.cpp Normal file
View file

@ -0,0 +1,262 @@
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
qe_mbp.cpp
Abstract:
Model-based projection utilities
Author:
Nikolaj Bjorner (nbjorner) 2015-5-29
Revision History:
--*/
#include "ast/rewriter/expr_safe_replace.h"
#include "ast/ast_pp.h"
#include "ast/ast_util.h"
#include "ast/occurs.h"
#include "ast/rewriter/th_rewriter.h"
#include "ast/expr_functors.h"
#include "ast/for_each_expr.h"
#include "ast/scoped_proof.h"
#include "qe/mbp/mbp_plugin.h"
#include "model/model_pp.h"
#include "model/model_evaluator.h"
namespace mbp {
struct noop_op_proc {
void operator()(expr*) {}
};
void project_plugin::mark_rec(expr_mark& visited, expr* e) {
for_each_expr_proc<noop_op_proc> fe;
for_each_expr(fe, visited, e);
}
void project_plugin::mark_rec(expr_mark& visited, expr_ref_vector const& es) {
for (expr* e : es)
mark_rec(visited, e);
}
/**
\brief return two terms that are equal in the model.
The distinct term t is false in model, so there
are at least two arguments of t that are equal in the model.
*/
expr_ref project_plugin::pick_equality(ast_manager& m, model& model, expr* t) {
SASSERT(m.is_distinct(t));
expr_ref val(m);
expr_ref_vector vals(m);
obj_map<expr, expr*> val2expr;
app* alit = to_app(t);
if (alit->get_num_args() == 2) {
return expr_ref(m.mk_eq(alit->get_arg(0), alit->get_arg(1)), m);
}
for (expr* e1 : *alit) {
expr* e2;
val = model(e1);
TRACE("qe", tout << mk_pp(e1, m) << " |-> " << val << "\n";);
if (val2expr.find(val, e2)) {
return expr_ref(m.mk_eq(e1, e2), m);
}
val2expr.insert(val, e1);
vals.push_back(val);
}
for (unsigned i = 0; i < alit->get_num_args(); ++i) {
for (unsigned j = i + 1; j < alit->get_num_args(); ++j) {
expr* e1 = alit->get_arg(i);
expr* e2 = alit->get_arg(j);
val = m.mk_eq(e1, e2);
if (!model.is_false(val))
return expr_ref(m.mk_eq(e1, e2), m);
}
}
UNREACHABLE();
return expr_ref(nullptr, m);
}
void project_plugin::erase(expr_ref_vector& lits, unsigned& i) {
lits[i] = lits.back();
lits.pop_back();
--i;
}
void project_plugin::push_back(expr_ref_vector& lits, expr* e) {
if (!m.is_true(e))
lits.push_back(e);
}
void project_plugin::extract_literals(model& model, app_ref_vector const& vars, expr_ref_vector& fmls) {
m_bool_visited.reset();
expr_ref val(m);
model_evaluator eval(model);
eval.set_expand_array_equalities(true);
TRACE("qe", tout << fmls << "\n";);
for (unsigned i = 0; i < fmls.size(); ++i) {
expr* fml = fmls.get(i), * nfml, * f1, * f2, * f3;
SASSERT(m.is_bool(fml));
if (m.is_not(fml, nfml) && m.is_distinct(nfml))
fmls[i--] = mbp::project_plugin::pick_equality(m, model, nfml);
else if (m.is_or(fml)) {
for (expr* arg : *to_app(fml)) {
val = eval(arg);
if (m.is_true(val)) {
fmls[i] = arg;
--i;
break;
}
}
}
else if (m.is_and(fml)) {
fmls.append(to_app(fml)->get_num_args(), to_app(fml)->get_args());
mbp::project_plugin::erase(fmls, i);
}
else if (m.is_iff(fml, f1, f2) || (m.is_not(fml, nfml) && m.is_xor(nfml, f1, f2))) {
val = eval(f1);
if (m.is_false(val)) {
f1 = mk_not(m, f1);
f2 = mk_not(m, f2);
}
fmls[i--] = f1;
push_back(fmls, f2);
}
else if (m.is_implies(fml, f1, f2)) {
val = eval(f2);
if (m.is_true(val)) {
fmls[i] = f2;
}
else {
fmls[i] = mk_not(m, f1);
}
--i;
}
else if (m.is_ite(fml, f1, f2, f3)) {
val = eval(f1);
if (m.is_true(val)) {
push_back(fmls, f1);
push_back(fmls, f2);
}
else {
push_back(fmls, mk_not(m, f1));
push_back(fmls, f3);
}
mbp::project_plugin::erase(fmls, i);
}
else if (m.is_not(fml, nfml) && m.is_not(nfml, nfml)) {
push_back(fmls, nfml);
mbp::project_plugin::erase(fmls, i);
}
else if (m.is_not(fml, nfml) && m.is_and(nfml)) {
for (expr* arg : *to_app(nfml)) {
val = eval(arg);
if (m.is_false(val)) {
fmls[i--] = mk_not(m, arg);
break;
}
}
}
else if (m.is_not(fml, nfml) && m.is_or(nfml)) {
for (expr* arg : *to_app(nfml))
push_back(fmls, mk_not(m, arg));
mbp::project_plugin::erase(fmls, i);
}
else if ((m.is_not(fml, nfml) && m.is_iff(nfml, f1, f2)) || m.is_xor(fml, f1, f2)) {
val = eval(f1);
if (m.is_true(val)) {
f2 = mk_not(m, f2);
}
else {
f1 = mk_not(m, f1);
}
push_back(fmls, f1);
push_back(fmls, f2);
mbp::project_plugin::erase(fmls, i);
}
else if (m.is_not(fml, nfml) && m.is_implies(nfml, f1, f2)) {
push_back(fmls, f1);
push_back(fmls, mk_not(m, f2));
mbp::project_plugin::erase(fmls, i);
}
else if (m.is_not(fml, nfml) && m.is_ite(nfml, f1, f2, f3)) {
val = eval(f1);
if (m.is_true(val)) {
push_back(fmls, f1);
push_back(fmls, mk_not(m, f2));
}
else {
push_back(fmls, mk_not(m, f1));
push_back(fmls, mk_not(m, f3));
}
mbp::project_plugin::erase(fmls, i);
}
else if (m.is_not(fml, nfml)) {
if (extract_bools(eval, fmls, nfml)) {
mbp::project_plugin::erase(fmls, i);
}
}
else if (extract_bools(eval, fmls, fml))
mbp::project_plugin::erase(fmls, i);
}
TRACE("qe", tout << fmls << "\n";);
}
bool project_plugin::extract_bools(model_evaluator& eval, expr_ref_vector& fmls, expr* fml) {
TRACE("qe", tout << "extract bools: " << mk_pp(fml, m) << "\n";);
ptr_vector<expr> todo;
expr_safe_replace sub(m);
m_visited.reset();
bool found_bool = false;
if (is_app(fml)) {
todo.append(to_app(fml)->get_num_args(), to_app(fml)->get_args());
}
while (!todo.empty() && m.inc()) {
expr* e = todo.back();
todo.pop_back();
if (m_visited.is_marked(e)) {
continue;
}
m_visited.mark(e);
if (m.is_bool(e) && !m.is_true(e) && !m.is_false(e)) {
expr_ref val = eval(e);
TRACE("qe", tout << "found: " << mk_pp(e, m) << " " << val << "\n";);
if (!m.inc())
continue;
if (!m.is_true(val) && !m.is_false(val) && contains_uninterpreted(val))
throw default_exception("could not evaluate Boolean in model");
SASSERT(m.is_true(val) || m.is_false(val));
if (!m_bool_visited.is_marked(e))
fmls.push_back(m.is_true(val) ? e : mk_not(m, e));
sub.insert(e, val);
m_bool_visited.mark(e);
found_bool = true;
}
else if (is_app(e)) {
todo.append(to_app(e)->get_num_args(), to_app(e)->get_args());
}
else {
TRACE("qe", tout << "expression not handled " << mk_pp(e, m) << "\n";);
}
}
if (found_bool) {
expr_ref tmp(m);
sub(fml, tmp);
expr_ref val = eval(tmp);
if (!m.is_true(val) && !m.is_false(val))
return false;
fmls.push_back(m.is_true(val) ? tmp : mk_not(m, tmp));
}
return found_bool;
}
}

93
src/qe/mbp/mbp_plugin.h Normal file
View file

@ -0,0 +1,93 @@
/*++
Copyright (c) 2015 Microsoft Corporation
Module Name:
mbp_plugin.h
Abstract:
Model-based projection utilities
Author:
Nikolaj Bjorner (nbjorner) 2015-5-28
Revision History:
--*/
#pragma once
#include "ast/ast.h"
#include "util/params.h"
#include "model/model.h"
#include "math/simplex/model_based_opt.h"
namespace mbp {
struct cant_project {};
struct def {
expr_ref var, term;
def(const expr_ref& v, expr_ref& t): var(v), term(t) {}
};
class project_plugin {
ast_manager& m;
expr_mark m_visited;
expr_mark m_bool_visited;
bool extract_bools(model_evaluator& eval, expr_ref_vector& fmls, expr* fml);
// over-approximation
bool contains_uninterpreted(expr* v) { return true; }
void push_back(expr_ref_vector& lits, expr* lit);
public:
project_plugin(ast_manager& m) :m(m) {}
virtual ~project_plugin() {}
virtual bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) { return false; }
/**
\brief partial solver.
*/
virtual bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) { return false; }
virtual family_id get_family_id() { return null_family_id; }
virtual void operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { };
/**
\brief project vars modulo model, return set of definitions for eliminated variables.
- vars in/out: returns variables that were not eliminated
- lits in/out: returns projected literals
- returns set of definitions
(TBD: in triangular form, the last definition can be substituted into definitions that come before)
*/
virtual vector<def> project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { return vector<def>(); }
/**
\brief model based saturation. Saturates theory axioms to equi-satisfiable literals over EUF,
such that 'shared' are not retained for EUF.
*/
virtual void saturate(model& model, func_decl_ref_vector const& shared, expr_ref_vector& lits) {}
/*
* extract top-level literals
*/
void extract_literals(model& model, app_ref_vector const& vars, expr_ref_vector& fmls);
/*
* Purify literals into linear inequalities or constraints without arithmetic variables.
*/
void purify(model& model, app_ref_vector const& vars, expr_ref_vector& fmls);
static expr_ref pick_equality(ast_manager& m, model& model, expr* t);
static void erase(expr_ref_vector& lits, unsigned& i);
static void mark_rec(expr_mark& visited, expr* e);
static void mark_rec(expr_mark& visited, expr_ref_vector const& es);
};
}

View file

@ -0,0 +1,438 @@
/**
Copyright (c) 2018 Microsoft Corporation
Module Name:
qe_solve.cpp
Abstract:
Light-weight variable solving plugin model for qe-lite and term_graph.
Author:
Nikolaj Bjorner (nbjorner), Arie Gurfinkel 2018-6-8
Revision History:
--*/
#include "ast/arith_decl_plugin.h"
#include "ast/bv_decl_plugin.h"
#include "ast/datatype_decl_plugin.h"
#include "ast/ast_util.h"
#include "ast/ast_pp.h"
#include "qe/mbp/mbp_solve_plugin.h"
namespace mbp {
expr_ref solve_plugin::operator()(expr* lit) {
if (m.is_not(lit, lit))
return solve(lit, false);
else
return solve(lit, true);
}
class arith_solve_plugin : public solve_plugin {
arith_util a;
public:
arith_solve_plugin(ast_manager& m, is_variable_proc& is_var): solve_plugin(m, m.get_family_id("arith"), is_var), a(m) {}
typedef std::pair<bool,expr*> signed_expr;
/**
*\brief
* return r * (sum_{(sign,e) \in exprs} sign * e)
*/
expr_ref mk_term(bool is_int, rational const& r, bool sign, svector<signed_expr> const& exprs) {
expr_ref_vector result(m);
for (auto const& kv : exprs) {
bool sign2 = kv.first;
expr* e = kv.second;
rational r2(r);
if (sign == sign2) {
r2.neg();
}
if (!r2.is_one()) {
result.push_back(a.mk_mul(a.mk_numeral(r2, is_int), e));
}
else {
result.push_back(e);
}
}
return a.mk_add_simplify(result);
}
/**
* \brief return true if lhs = rhs can be solved as v = t, where v is a variable.
*/
bool solve(expr* lhs, expr* rhs, expr_ref& v, expr_ref& t) {
if (!a.is_int(lhs) && !a.is_real(rhs)) {
return false;
}
rational a_val;
bool is_int = a.is_int(lhs);
svector<signed_expr> todo, done;
todo.push_back(std::make_pair(true, lhs));
todo.push_back(std::make_pair(false, rhs));
while (!todo.empty()) {
expr* e = todo.back().second;
bool sign = todo.back().first;
todo.pop_back();
if (a.is_add(e)) {
for (expr* arg : *to_app(e)) {
todo.push_back(std::make_pair(sign, arg));
}
}
else if (a.is_sub(e)) {
app* sub = to_app(e);
todo.push_back(std::make_pair(sign, sub->get_arg(0)));
for (unsigned i = 1; i < sub->get_num_args(); ++i) {
todo.push_back(std::make_pair(!sign, sub->get_arg(i)));
}
}
else if (a.is_uminus(e)) {
todo.push_back(std::make_pair(!sign, to_app(e)->get_arg(0)));
}
else if (is_invertible_mul(is_int, e, a_val)) {
done.append(todo);
v = e;
a_val = rational(1)/a_val;
t = mk_term(is_int, a_val, sign, done);
TRACE("qe", tout << mk_pp(e, m) << " := " << t << "\n";);
return true;
}
else {
done.push_back(std::make_pair(sign, e));
}
}
return false;
}
// is x a constant and can we safely divide by x to solve for a variable?
bool is_invertible_const(bool is_int, expr* x, rational& a_val) {
expr* y;
if (a.is_uminus(x, y) && is_invertible_const(is_int, y, a_val)) {
a_val.neg();
return true;
}
else if (a.is_numeral(x, a_val) && !a_val.is_zero()) {
if (!is_int || a_val.is_one() || a_val.is_minus_one()) {
return true;
}
}
return false;
}
// is arg of the form a_val * v, where a_val
// is a constant that we can safely divide by.
bool is_invertible_mul(bool is_int, expr*& arg, rational& a_val) {
if (is_variable(arg)) {
a_val = rational(1);
return true;
}
expr* x, *y;
if (a.is_mul(arg, x, y)) {
if (is_variable(x) && is_invertible_const(is_int, y, a_val)) {
arg = x;
return true;
}
if (is_variable(y) && is_invertible_const(is_int, x, a_val)) {
arg = y;
return true;
}
}
return false;
}
expr_ref mk_eq_core(expr *e1, expr *e2) {
expr_ref v(m), t(m);
if (solve(e1, e2, v, t)) {
return expr_ref(m.mk_eq(v, t), m);
}
if (a.is_zero(e1)) {
std::swap(e1, e2);
}
// y + -1*x == 0 --> y = x
expr *a0 = nullptr, *a1 = nullptr, *x = nullptr;
if (a.is_zero(e2) && a.is_add(e1, a0, a1)) {
if (a.is_times_minus_one(a1, x)) {
e1 = a0;
e2 = x;
}
else if (a.is_times_minus_one(a0, x)) {
e1 = a1;
e2 = x;
}
}
return expr_ref(m.mk_eq(e1, e2), m);
}
app* mk_le_zero(expr *arg) {
expr *e1, *e2, *e3;
if (a.is_add(arg, e1, e2)) {
// e1-e2<=0 --> e1<=e2
if (a.is_times_minus_one(e2, e3)) {
return a.mk_le(e1, e3);
}
// -e1+e2<=0 --> e2<=e1
else if (a.is_times_minus_one(e1, e3)) {
return a.mk_le(e2, e3);
}
}
return a.mk_le(arg, mk_zero());
}
app* mk_ge_zero(expr *arg) {
expr *e1, *e2, *e3;
if (a.is_add(arg, e1, e2)) {
// e1-e2>=0 --> e1>=e2
if (a.is_times_minus_one(e2, e3)) {
return a.mk_ge(e1, e3);
}
// -e1+e2>=0 --> e2>=e1
else if (a.is_times_minus_one(e1, e3)) {
return a.mk_ge(e2, e3);
}
}
return a.mk_ge(arg, mk_zero());
}
bool mk_le_core(expr *arg1, expr * arg2, expr_ref &result) {
// t <= -1 ==> t < 0 ==> ! (t >= 0)
rational n;
if (a.is_int (arg1) && a.is_minus_one (arg2)) {
result = m.mk_not (mk_ge_zero (arg1));
return true;
}
else if (a.is_zero(arg2)) {
result = mk_le_zero(arg1);
return true;
}
else if (a.is_int(arg1) && a.is_numeral(arg2, n) && n < 0) {
// t <= n ==> t < n + 1 ==> ! (t >= n + 1)
result = m.mk_not(a.mk_ge(arg1, a.mk_numeral(n+1, true)));
return true;
}
return false;
}
expr * mk_zero() {
return a.mk_numeral (rational (0), true);
}
bool is_one(expr const * n) const {
rational val;
return a.is_numeral (n, val) && val.is_one ();
}
bool mk_ge_core(expr * arg1, expr * arg2, expr_ref &result) {
// t >= 1 ==> t > 0 ==> ! (t <= 0)
rational n;
if (a.is_int (arg1) && is_one (arg2)) {
result = m.mk_not (mk_le_zero (arg1));
return true;
}
else if (a.is_zero(arg2)) {
result = mk_ge_zero(arg1);
return true;
}
else if (a.is_int(arg1) && a.is_numeral(arg2, n) && n > 0) {
// t >= n ==> t > n - 1 ==> ! (t <= n - 1)
result = m.mk_not(a.mk_le(arg1, a.mk_numeral(n-1, true)));
return true;
}
return false;
}
expr_ref solve(expr* atom, bool is_pos) override {
expr *e1, *e2;
expr_ref res(atom, m);
if (m.is_eq (atom, e1, e2)) {
expr_ref v(m), t(m);
v = e1; t = e2;
// -- attempt to solve using arithmetic
solve(e1, e2, v, t);
// -- normalize equality
res = mk_eq_core(v, t);
}
else if (a.is_le(atom, e1, e2)) {
mk_le_core(e1, e2, res);
}
else if (a.is_ge(atom, e1, e2)) {
mk_ge_core(e1, e2, res);
}
// restore negation
if (!is_pos) {
res = mk_not(m, res);
}
return res;
}
};
class basic_solve_plugin : public solve_plugin {
public:
basic_solve_plugin(ast_manager& m, is_variable_proc& is_var):
solve_plugin(m, m.get_basic_family_id(), is_var) {}
expr_ref solve(expr *atom, bool is_pos) override {
expr_ref res(atom, m);
expr* lhs = nullptr, *rhs = nullptr, *n = nullptr;
if (m.is_eq(atom, lhs, rhs)) {
if (m.is_not(lhs, n) && is_variable(n)) {
res = m.mk_eq(n, mk_not(m, rhs));
}
else if (m.is_not(rhs, n) && is_variable(n)) {
res = m.mk_eq(n, mk_not(m, lhs));
}
else if (is_variable(rhs) && !is_variable(lhs)) {
res = m.mk_eq(rhs, lhs);
}
}
// (ite cond (= VAR t) (= VAR t2)) case
expr* cond = nullptr, *th = nullptr, *el = nullptr;
if (m.is_ite(atom, cond, th, el)) {
expr_ref r1 = solve(th, true);
expr_ref r2 = solve(el, true);
expr* v1 = nullptr, *t1 = nullptr, *v2 = nullptr, *t2 = nullptr;
if (m.is_eq(r1, v1, t1) && m.is_eq(r2, v2, t2) && v1 == v2) {
res = m.mk_eq(v1, m.mk_ite(cond, t1, t2));
}
}
if (is_variable(atom) && m.is_bool(atom)) {
res = m.mk_eq(atom, m.mk_bool_val(is_pos));
return res;
}
return is_pos ? res : mk_not(res);
}
};
class dt_solve_plugin : public solve_plugin {
datatype_util dt;
public:
dt_solve_plugin(ast_manager& m, is_variable_proc& is_var):
solve_plugin(m, m.get_family_id("datatype"), is_var),
dt(m) {}
expr_ref solve(expr *atom, bool is_pos) override {
expr_ref res(atom, m);
expr* lhs = nullptr, *rhs = nullptr;
if (m.is_eq(atom, lhs, rhs)) {
if (dt.is_constructor(rhs)) {
std::swap(lhs, rhs);
}
if (dt.is_constructor(lhs) && dt.is_constructor(rhs)) {
app* l = to_app(lhs), *r = to_app(rhs);
if (l->get_decl() == r->get_decl()) {
expr_ref_vector eqs(m);
for (unsigned i = 0, sz = l->get_num_args(); i < sz; ++i) {
eqs.push_back(m.mk_eq(l->get_arg(i), r->get_arg(i)));
}
res = mk_and(eqs);
}
else {
res = m.mk_false();
}
}
else if (dt.is_constructor(lhs)) {
app* l = to_app(lhs);
func_decl* d = l->get_decl();
expr_ref_vector conjs(m);
conjs.push_back(dt.mk_is(d, rhs));
ptr_vector<func_decl> const& acc = *dt.get_constructor_accessors(d);
for (unsigned i = 0; i < acc.size(); ++i) {
conjs.push_back(m.mk_eq(l->get_arg(i), m.mk_app(acc[i], rhs)));
}
res = mk_and(conjs);
}
}
// TBD: can also solve for is_nil(x) by x = nil
//
return is_pos ? res : mk_not(res);
}
};
class bv_solve_plugin : public solve_plugin {
bv_util m_bv;
bool solve_eq(expr*& lhs, expr*& rhs) {
unsigned lo, hi;
expr* e = nullptr;
if (m_bv.is_extract(lhs, lo, hi, e) && is_variable(e)) {
lhs = e;
unsigned sz = m_bv.get_bv_size(e);
if (lo > 0 && hi + 1 < sz) {
expr* args[3] = { m_bv.mk_extract(sz-1, hi + 1, e), rhs, m_bv.mk_extract(lo - 1, 0, e)};
rhs = m_bv.mk_concat(3, args);
}
else if (lo == 0 && hi + 1 < sz) {
expr* args[2] = { m_bv.mk_extract(sz-1, hi + 1, e), rhs };
rhs = m_bv.mk_concat(2, args);
}
else if (lo > 0 && hi + 1 == sz) {
expr* args[3] = { rhs, m_bv.mk_extract(lo - 1, 0, e) };
rhs = m_bv.mk_concat(2, args);
}
else {
return false;
}
return true;
}
return false;
}
public:
bv_solve_plugin(ast_manager& m, is_variable_proc& is_var):
solve_plugin(m, m.get_family_id("bv"), is_var), m_bv(m) {}
expr_ref solve(expr *atom, bool is_pos) override {
expr_ref res(atom, m);
expr* lhs = nullptr, *rhs = nullptr;
if (is_pos && m.is_eq(atom, lhs, rhs) && solve_eq(lhs, rhs)) {
res = m.mk_eq(lhs, rhs);
return res;
}
if (is_pos && m.is_eq(atom, lhs, rhs) && solve_eq(rhs, lhs)) {
res = m.mk_eq(rhs, lhs);
return res;
}
return is_pos ? res : mk_not(res);
}
};
class array_solve_plugin : public solve_plugin {
public:
array_solve_plugin(ast_manager& m, is_variable_proc& is_var): solve_plugin(m, m.get_family_id("array"), is_var) {}
expr_ref solve(expr *atom, bool is_pos) override {
expr_ref res(atom, m);
return is_pos ? res : mk_not(res);
}
};
solve_plugin* mk_basic_solve_plugin(ast_manager& m, is_variable_proc& is_var) {
return alloc(basic_solve_plugin, m, is_var);
}
solve_plugin* mk_arith_solve_plugin(ast_manager& m, is_variable_proc& is_var) {
return alloc(arith_solve_plugin, m, is_var);
}
solve_plugin* mk_dt_solve_plugin(ast_manager& m, is_variable_proc& is_var) {
return alloc(dt_solve_plugin, m, is_var);
}
solve_plugin* mk_bv_solve_plugin(ast_manager& m, is_variable_proc& is_var) {
return alloc(bv_solve_plugin, m, is_var);
}
#if 0
solve_plugin* mk_array_solve_plugin(ast_manager& m, is_variable_proc& is_var) {
return alloc(array_solve_plugin, m, is_var);
}
#endif
}

View file

@ -0,0 +1,54 @@
/**
Copyright (c) 2018 Microsoft Corporation
Module Name:
mbp_solve_plugin.h
Abstract:
Light-weight variable solving plugin model for qe-lite and term_graph.
Author:
Nikolaj Bjorner (nbjorner), Arie Gurfinkel 2018-6-8
Revision History:
--*/
#pragma once
#include "ast/ast.h"
#include "ast/is_variable_test.h"
#include "util/plugin_manager.h"
namespace mbp {
class solve_plugin {
protected:
ast_manager& m;
family_id m_id;
is_variable_proc& m_is_var;
virtual expr_ref solve(expr* atom, bool is_pos) = 0;
bool is_variable(expr* e) const { return m_is_var(e); }
public:
solve_plugin(ast_manager& m, family_id fid, is_variable_proc& is_var) : m(m), m_id(fid), m_is_var(is_var) {}
virtual ~solve_plugin() {}
family_id get_family_id() const { return m_id; }
/// Process (and potentially augment) a literal
expr_ref operator() (expr *lit);
};
solve_plugin* mk_basic_solve_plugin(ast_manager& m, is_variable_proc& is_var);
solve_plugin* mk_arith_solve_plugin(ast_manager& m, is_variable_proc& is_var);
solve_plugin* mk_dt_solve_plugin(ast_manager& m, is_variable_proc& is_var);
solve_plugin* mk_bv_solve_plugin(ast_manager& m, is_variable_proc& is_var);
// solve_plugin* mk_array_solve_plugin(ast_manager& m, is_variable_proc& is_var);
}

File diff suppressed because it is too large Load diff

156
src/qe/mbp/mbp_term_graph.h Normal file
View file

@ -0,0 +1,156 @@
/**++
Copyright (c) Arie Gurfinkel
Module Name:
mbp_term_graph.h
Abstract:
Equivalence graph of terms
Author:
Arie Gurfinkel
Notes:
--*/
#pragma once
#include "ast/ast.h"
#include "ast/is_variable_test.h"
#include "util/plugin_manager.h"
#include "qe/mbp/mbp_solve_plugin.h"
#include "model/model.h"
namespace mbp {
class term;
class term_graph {
class projector;
class is_variable_proc : public ::is_variable_proc {
bool m_exclude;
obj_hashtable<func_decl> m_decls, m_solved;
public:
bool operator()(const expr *e) const override;
bool operator()(const term &t) const;
void set_decls(const func_decl_ref_vector &decls, bool exclude);
void mark_solved(const expr *e);
void reset_solved() {m_solved.reset();}
void reset() {m_decls.reset(); m_solved.reset(); m_exclude = true;}
};
struct term_hash { unsigned operator()(term const* t) const; };
struct term_eq { bool operator()(term const* a, term const* b) const; };
ast_manager & m;
ptr_vector<term> m_terms;
expr_ref_vector m_lits; // NSB: expr_ref_vector?
u_map<term* > m_app2term;
ast_ref_vector m_pinned;
projector* m_projector;
u_map<expr*> m_term2app;
plugin_manager<solve_plugin> m_plugins;
ptr_hashtable<term, term_hash, term_eq> m_cg_table;
vector<std::pair<term*,term*>> m_merge;
term_graph::is_variable_proc m_is_var;
void merge(term &t1, term &t2);
void merge_flush();
term *mk_term(expr *t);
term *get_term(expr *t);
term *internalize_term(expr *t);
void internalize_eq(expr *a1, expr *a2);
void internalize_lit(expr *lit);
bool is_internalized(expr *a);
bool term_lt(term const &t1, term const &t2);
void pick_root (term &t);
void pick_roots();
void reset_marks();
bool marks_are_clear();
expr* mk_app_core(expr* a);
expr_ref mk_app(term const &t);
expr* mk_pure(term& t);
expr_ref mk_app(expr *a);
void mk_equalities(term const &t, expr_ref_vector &out);
void mk_all_equalities(term const &t, expr_ref_vector &out);
void display(std::ostream &out);
bool is_pure_def(expr* atom, expr *& v);
public:
term_graph(ast_manager &m);
~term_graph();
void set_vars(func_decl_ref_vector const& decls, bool exclude);
ast_manager& get_ast_manager() const { return m;}
void add_lit(expr *lit);
void add_lits(expr_ref_vector const &lits) { for (expr* e : lits) add_lit(e); }
void add_eq(expr* a, expr* b) { internalize_eq(a, b); }
void reset();
// deprecate?
void to_lits(expr_ref_vector &lits, bool all_equalities = false);
expr_ref to_expr();
/**
* Return literals obtained by projecting added literals
* onto the vocabulary of decls (if exclude is false) or outside the
* vocabulary of decls (if exclude is true).
*/
expr_ref_vector project();
expr_ref_vector solve();
expr_ref_vector project(model &mdl);
/**
* Return disequalities to ensure that disequalities between
* excluded functions are preserved.
* For example if f(a) = b, f(c) = d, and b and d are not
* congruent, then produce the disequality a != c.
*/
expr_ref_vector get_ackerman_disequalities();
/**
* Produce model-based disequality
* certificate corresponding to
* definition in BGVS 2020.
* A disequality certificate is a reduced set of
* disequalities, true under mdl, such that the literals
* can be satisfied when non-shared symbols are projected.
*/
expr_ref_vector dcert(model& mdl, expr_ref_vector const& lits);
/**
* Produce a model-based partition.
*/
vector<expr_ref_vector> get_partition(model& mdl);
/**
* Extract shared occurrences of terms whose sort are
* fid, but appear in a context that is not fid.
* for example f(x + y) produces the shared occurrence
* x + y when f is uninterpreted and x + y has sort Int or Real.
*/
expr_ref_vector shared_occurrences(family_id fid);
/**
* Map expression that occurs in added literals into representative if it exists.
*/
void add_model_based_terms(model& mdl, expr_ref_vector const& terms);
expr* rep_of(expr* e);
};
}