mirror of
https://github.com/Z3Prover/z3
synced 2025-04-11 03:33:35 +00:00
430 lines
14 KiB
C++
430 lines
14 KiB
C++
/*++
|
|
Copyright (c) 2012 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
diff_neq_tactic.cpp
|
|
|
|
Abstract:
|
|
|
|
Solver for integer problems that contains literals of the form
|
|
k <= x
|
|
x <= k
|
|
x - y != k
|
|
And all variables are bounded.
|
|
|
|
Author:
|
|
|
|
Leonardo de Moura (leonardo) 2012-02-07.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
#include"tactical.h"
|
|
#include"arith_decl_plugin.h"
|
|
#include"ast_smt2_pp.h"
|
|
#include"model.h"
|
|
|
|
class diff_neq_tactic : public tactic {
|
|
struct imp {
|
|
ast_manager & m;
|
|
arith_util u;
|
|
typedef unsigned var;
|
|
|
|
expr_ref_vector m_var2expr;
|
|
obj_map<expr, var> m_expr2var;
|
|
|
|
svector<int> m_lower;
|
|
svector<int> m_upper;
|
|
struct diseq {
|
|
var m_y;
|
|
int m_k;
|
|
diseq(var y, int k):m_y(y), m_k(k) {}
|
|
};
|
|
typedef svector<diseq> diseqs;
|
|
vector<diseqs> m_var_diseqs;
|
|
typedef svector<int> decision_stack;
|
|
decision_stack m_stack;
|
|
volatile bool m_cancel;
|
|
|
|
bool m_produce_models;
|
|
rational m_max_k;
|
|
rational m_max_neg_k;
|
|
|
|
unsigned m_num_conflicts;
|
|
|
|
imp(ast_manager & _m, params_ref const & p):
|
|
m(_m),
|
|
u(m),
|
|
m_var2expr(m) {
|
|
updt_params(p);
|
|
m_cancel = false;
|
|
}
|
|
|
|
void updt_params(params_ref const & p) {
|
|
m_max_k = rational(p.get_uint(":diff-neq-max-k", 1024));
|
|
m_max_neg_k = -m_max_k;
|
|
if (m_max_k >= rational(INT_MAX/2))
|
|
m_max_k = rational(INT_MAX/2);
|
|
}
|
|
|
|
void set_cancel(bool f) {
|
|
m_cancel = f;
|
|
}
|
|
|
|
void throw_not_supported() {
|
|
throw tactic_exception("goal is not diff neq");
|
|
}
|
|
|
|
unsigned num_vars() const {
|
|
return m_upper.size();
|
|
}
|
|
|
|
var mk_var(expr * t) {
|
|
SASSERT(is_uninterp_const(t));
|
|
var x;
|
|
if (m_expr2var.find(t, x))
|
|
return x;
|
|
x = m_upper.size();
|
|
m_expr2var.insert(t, x);
|
|
m_var2expr.push_back(t);
|
|
m_lower.push_back(INT_MIN); // unknown
|
|
m_upper.push_back(INT_MAX); // unknown
|
|
m_var_diseqs.push_back(diseqs());
|
|
return x;
|
|
}
|
|
|
|
void process_le(expr * lhs, expr * rhs) {
|
|
if (!u.is_int(lhs))
|
|
throw_not_supported();
|
|
rational k;
|
|
if (is_uninterp_const(lhs) && u.is_numeral(rhs, k) && m_max_neg_k <= k && k <= m_max_k) {
|
|
var x = mk_var(lhs);
|
|
int _k = static_cast<int>(k.get_int64());
|
|
m_upper[x] = _k;
|
|
|
|
}
|
|
else if (is_uninterp_const(rhs) && u.is_numeral(lhs, k) && m_max_neg_k <= k && k <= m_max_k) {
|
|
var x = mk_var(rhs);
|
|
int _k = static_cast<int>(k.get_int64());
|
|
m_lower[x] = _k;
|
|
}
|
|
else {
|
|
throw_not_supported();
|
|
}
|
|
}
|
|
|
|
// process t1 - t2 != k
|
|
void process_neq_core(expr * t1, expr * t2, int k) {
|
|
var x1 = mk_var(t1);
|
|
var x2 = mk_var(t2);
|
|
if (x1 == x2)
|
|
throw_not_supported(); // must simplify first
|
|
if (x1 < x2) {
|
|
std::swap(x1, x2);
|
|
k = -k;
|
|
}
|
|
m_var_diseqs[x1].push_back(diseq(x2, k));
|
|
}
|
|
|
|
void process_neq(expr * lhs, expr * rhs) {
|
|
if (!u.is_int(lhs))
|
|
throw_not_supported();
|
|
if (is_uninterp_const(lhs) && is_uninterp_const(rhs)) {
|
|
process_neq_core(lhs, rhs, 0);
|
|
return;
|
|
}
|
|
if (u.is_numeral(lhs))
|
|
std::swap(lhs, rhs);
|
|
rational k;
|
|
if (!u.is_numeral(rhs, k))
|
|
throw_not_supported();
|
|
if (!(m_max_neg_k <= k && k <= m_max_k))
|
|
throw_not_supported();
|
|
int _k = static_cast<int>(k.get_int64());
|
|
expr * t1, * t2, * mt1, * mt2;
|
|
if (u.is_add(lhs, t1, t2)) {
|
|
if (is_uninterp_const(t1) && u.is_times_minus_one(t2, mt2) && is_uninterp_const(mt2))
|
|
process_neq_core(t1, mt2, _k);
|
|
else if (is_uninterp_const(t2) && u.is_times_minus_one(t1, mt1) && is_uninterp_const(mt1))
|
|
process_neq_core(t2, mt1, _k);
|
|
else
|
|
throw_not_supported();
|
|
}
|
|
else {
|
|
throw_not_supported();
|
|
}
|
|
}
|
|
|
|
// throws exception if contains unbounded variable
|
|
void check_unbounded() {
|
|
unsigned num = num_vars();
|
|
for (var x = 0; x < num; x++) {
|
|
if (m_lower[x] == INT_MIN || m_upper[x] == INT_MAX)
|
|
throw_not_supported();
|
|
// possible extension: support bound normalization here
|
|
if (m_lower[x] != 0)
|
|
throw_not_supported(); // use bound normalizer
|
|
}
|
|
}
|
|
|
|
void compile(goal const & g) {
|
|
expr * lhs;
|
|
expr * rhs;
|
|
unsigned sz = g.size();
|
|
for (unsigned i = 0; i < sz; i++) {
|
|
expr * f = g.form(i);
|
|
TRACE("diff_neq_tactic", tout << "processing: " << mk_ismt2_pp(f, m) << "\n";);
|
|
if (u.is_le(f, lhs, rhs))
|
|
process_le(lhs, rhs);
|
|
else if (u.is_ge(f, lhs, rhs))
|
|
process_le(rhs, lhs);
|
|
else if (m.is_not(f, f) && m.is_eq(f, lhs, rhs))
|
|
process_neq(lhs, rhs);
|
|
else
|
|
throw_not_supported();
|
|
}
|
|
check_unbounded();
|
|
}
|
|
|
|
void display(std::ostream & out) {
|
|
unsigned num = num_vars();
|
|
for (var x = 0; x < num; x++) {
|
|
out << m_lower[x] << " <= " << mk_ismt2_pp(m_var2expr.get(x), m) << " <= " << m_upper[x] << "\n";
|
|
}
|
|
for (var x = 0; x < num; x++) {
|
|
diseqs::iterator it = m_var_diseqs[x].begin();
|
|
diseqs::iterator end = m_var_diseqs[x].end();
|
|
for (; it != end; ++it) {
|
|
out << mk_ismt2_pp(m_var2expr.get(x), m) << " != " << mk_ismt2_pp(m_var2expr.get(it->m_y), m) << " + " << it->m_k << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void display_model(std::ostream & out) {
|
|
unsigned num = m_stack.size();
|
|
for (var x = 0; x < num; x++) {
|
|
out << mk_ismt2_pp(m_var2expr.get(x), m) << " := " << m_stack[x] << "\n";
|
|
}
|
|
}
|
|
|
|
svector<bool> m_forbidden;
|
|
|
|
// make sure m_forbidden.size() > max upper bound
|
|
void init_forbidden() {
|
|
int max = 0;
|
|
unsigned num = num_vars();
|
|
for (var x = 0; x < num; x++) {
|
|
if (m_upper[x] > max)
|
|
max = m_upper[x];
|
|
}
|
|
m_forbidden.reset();
|
|
m_forbidden.resize(max+1, false);
|
|
}
|
|
|
|
// Return a value v s.t. v >= starting_at and v <= m_upper[x] and all diseqs in m_var_diseqs[x] are satisfied.
|
|
// Return -1 if such value does not exist.
|
|
int choose_value(var x, int starting_at) {
|
|
int max = starting_at-1;
|
|
int v = starting_at;
|
|
int upper = m_upper[x];
|
|
if (starting_at > upper)
|
|
return -1;
|
|
diseqs const & ds = m_var_diseqs[x];
|
|
diseqs::const_iterator it = ds.begin();
|
|
diseqs::const_iterator end = ds.end();
|
|
for (; it != end; ++it) {
|
|
int bad_v = m_stack[it->m_y] + it->m_k;
|
|
if (bad_v < v)
|
|
continue;
|
|
if (bad_v > upper)
|
|
continue;
|
|
if (bad_v == v) {
|
|
while (true) {
|
|
v++;
|
|
if (v > upper)
|
|
return -1;
|
|
if (!m_forbidden[v])
|
|
break;
|
|
m_forbidden[v] = false;
|
|
}
|
|
continue;
|
|
}
|
|
SASSERT(bad_v > v && bad_v <= upper);
|
|
m_forbidden[bad_v] = true;
|
|
if (bad_v > max)
|
|
max = bad_v;
|
|
}
|
|
// reset forbidden
|
|
for (int i = starting_at + 1; i <= max; i++)
|
|
m_forbidden[i] = false;
|
|
DEBUG_CODE({
|
|
for (unsigned i = 0; i < m_forbidden.size(); i++) {
|
|
SASSERT(!m_forbidden[i]);
|
|
}
|
|
});
|
|
return v;
|
|
}
|
|
|
|
bool extend_model(var x) {
|
|
int v = choose_value(x, 0);
|
|
if (v == -1)
|
|
return false;
|
|
m_stack.push_back(v);
|
|
return true;
|
|
}
|
|
|
|
bool resolve_conflict() {
|
|
m_num_conflicts++;
|
|
while (!m_stack.empty()) {
|
|
int v = m_stack.back();
|
|
m_stack.pop_back();
|
|
var x = m_stack.size();
|
|
v = choose_value(x, v+1);
|
|
if (v != -1) {
|
|
m_stack.push_back(v);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool search() {
|
|
m_num_conflicts = 0;
|
|
init_forbidden();
|
|
unsigned nvars = num_vars();
|
|
while (m_stack.size() < nvars) {
|
|
if (m_cancel)
|
|
throw tactic_exception(TACTIC_CANCELED_MSG);
|
|
TRACE("diff_neq_tactic", display_model(tout););
|
|
var x = m_stack.size();
|
|
if (extend_model(x))
|
|
continue;
|
|
if (!resolve_conflict())
|
|
return false;
|
|
}
|
|
TRACE("diff_neq_tactic", display_model(tout););
|
|
return true;
|
|
}
|
|
|
|
model * mk_model() {
|
|
model * md = alloc(model, m);
|
|
unsigned num = num_vars();
|
|
SASSERT(m_stack.size() == num);
|
|
for (var x = 0; x < num; x++) {
|
|
func_decl * d = to_app(m_var2expr.get(x))->get_decl();
|
|
md->register_decl(d, u.mk_numeral(rational(m_stack[x]), true));
|
|
}
|
|
return md;
|
|
}
|
|
|
|
virtual void operator()(goal_ref const & g,
|
|
goal_ref_buffer & result,
|
|
model_converter_ref & mc,
|
|
proof_converter_ref & pc,
|
|
expr_dependency_ref & core) {
|
|
SASSERT(g->is_well_sorted());
|
|
m_produce_models = g->models_enabled();
|
|
mc = 0; pc = 0; core = 0; result.reset();
|
|
tactic_report report("diff-neq", *g);
|
|
fail_if_proof_generation("diff-neq", g);
|
|
fail_if_unsat_core_generation("diff-neq", g);
|
|
if (g->inconsistent()) {
|
|
result.push_back(g.get());
|
|
return;
|
|
}
|
|
compile(*g);
|
|
TRACE("diff_neq_tactic", g->display(tout); display(tout););
|
|
bool r = search();
|
|
report_tactic_progress(":conflicts", m_num_conflicts);
|
|
if (r) {
|
|
if (m_produce_models)
|
|
mc = model2model_converter(mk_model());
|
|
g->reset();
|
|
}
|
|
else {
|
|
g->assert_expr(m.mk_false());
|
|
}
|
|
g->inc_depth();
|
|
result.push_back(g.get());
|
|
TRACE("diff_neq", g->display(tout););
|
|
SASSERT(g->is_well_sorted());
|
|
}
|
|
};
|
|
|
|
imp * m_imp;
|
|
params_ref m_params;
|
|
public:
|
|
diff_neq_tactic(ast_manager & m, params_ref const & p):
|
|
m_params(p) {
|
|
m_imp = alloc(imp, m, p);
|
|
}
|
|
|
|
virtual tactic * translate(ast_manager & m) {
|
|
return alloc(diff_neq_tactic, m, m_params);
|
|
}
|
|
|
|
virtual ~diff_neq_tactic() {
|
|
dealloc(m_imp);
|
|
}
|
|
|
|
virtual void updt_params(params_ref const & p) {
|
|
m_params = p;
|
|
m_imp->updt_params(p);
|
|
}
|
|
|
|
virtual void collect_param_descrs(param_descrs & r) {
|
|
r.insert(":diff-neq-max-k", CPK_UINT, "(default: 1024) maximum variable upper bound for diff neq solver.");
|
|
}
|
|
|
|
virtual void collect_statistics(statistics & st) const {
|
|
st.update("conflicts", m_imp->m_num_conflicts);
|
|
}
|
|
|
|
virtual void reset_statistics() {
|
|
m_imp->m_num_conflicts = 0;
|
|
}
|
|
|
|
/**
|
|
\brief Fix a DL variable in s to 0.
|
|
If s is not really in the difference logic fragment, then this is a NOOP.
|
|
*/
|
|
virtual void operator()(goal_ref const & in,
|
|
goal_ref_buffer & result,
|
|
model_converter_ref & mc,
|
|
proof_converter_ref & pc,
|
|
expr_dependency_ref & core) {
|
|
(*m_imp)(in, result, mc, pc, core);
|
|
}
|
|
|
|
virtual void cleanup() {
|
|
unsigned num_conflicts = m_imp->m_num_conflicts;
|
|
ast_manager & m = m_imp->m;
|
|
imp * d = m_imp;
|
|
#pragma omp critical (tactic_cancel)
|
|
{
|
|
d = m_imp;
|
|
}
|
|
dealloc(d);
|
|
d = alloc(imp, m, m_params);
|
|
#pragma omp critical (tactic_cancel)
|
|
{
|
|
m_imp = d;
|
|
}
|
|
m_imp->m_num_conflicts = num_conflicts;
|
|
}
|
|
|
|
protected:
|
|
virtual void set_cancel(bool f) {
|
|
if (m_imp)
|
|
m_imp->set_cancel(f);
|
|
}
|
|
};
|
|
|
|
tactic * mk_diff_neq_tactic(ast_manager & m, params_ref const & p) {
|
|
return clean(alloc(diff_neq_tactic, m, p));
|
|
}
|
|
|
|
|
|
|