3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-19 01:32:17 +00:00

add special procedures for UTVPI and horn arithmetic

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2013-04-28 12:47:55 -07:00
parent 4f9247a28a
commit 9158fb17c1
12 changed files with 3397 additions and 208 deletions

694
src/smt/theory_utvpi_def.h Normal file
View file

@ -0,0 +1,694 @@
/*++
Copyright (c) 2013 Microsoft Corporation
Module Name:
theory_utvpi_def.h
Abstract:
Implementation of UTVPI solver.
Author:
Nikolaj Bjorner (nbjorner) 2013-04-26
Revision History:
1. introduce x^+ and x^-, such that 2*x := x^+ - x^-
2. rewrite constraints as follows:
x - y <= k => x^+ - y^+ <= k
y^- - x^- <= k
x <= k => x^+ - x^- <= 2k
x + y <= k => x^+ - y^- <= k
y^+ - x^- <= k
- x - y <= k => x^- - y^+ <= k
y^- - x^+ <= k
3. Solve for x^+ and x^-
4. Check parity condition for integers (see Lahiri and Musuvathi 05)
5. extract model for M(x) := (M(x^+)- M(x^-))/2
--*/
#ifndef _THEORY_UTVPI_DEF_H_
#define _THEORY_UTVPI_DEF_H_
#include "theory_utvpi.h"
#include "heap.h"
#include "ast_pp.h"
#include "smt_context.h"
namespace smt {
template<typename Ext>
theory_utvpi<Ext>::theory_utvpi(ast_manager& m):
theory(m.mk_family_id("arith")),
a(m),
m_arith_eq_adapter(*this, m_params, a),
m_zero_int(null_theory_var),
m_zero_real(null_theory_var),
m_asserted_qhead(0),
m_nc_functor(*this),
m_agility(0.5),
m_lia(false),
m_lra(false),
m_non_utvpi_exprs(false),
m_test(m),
m_factory(0) {
}
template<typename Ext>
theory_utvpi<Ext>::~theory_utvpi() {
reset_eh();
}
template<typename Ext>
std::ostream& theory_utvpi<Ext>::atom::display(theory_utvpi const& th, std::ostream& out) const {
context& ctx = th.get_context();
lbool asgn = ctx.get_assignment(m_bvar);
bool sign = (l_undef == l_false);
return out << literal(m_bvar, sign) << " " << mk_pp(ctx.bool_var2expr(m_bvar), th.get_manager()) << " ";
if (l_undef == asgn) {
out << "unassigned\n";
}
else {
th.m_graph.display_edge(out, get_asserted_edge());
}
return out;
}
template<typename Ext>
theory_var theory_utvpi<Ext>::mk_var(enode* n) {
th_var v = theory::mk_var(n);
TRACE("utvpi", tout << v << " " << mk_pp(n->get_owner(), get_manager()) << "\n";);
m_graph.init_var(to_var(v));
m_graph.init_var(neg(to_var(v)));
get_context().attach_th_var(n, this, v);
return v;
}
template<typename Ext>
theory_var theory_utvpi<Ext>::mk_var(expr* n) {
context & ctx = get_context();
enode* e = 0;
th_var v = null_theory_var;
m_lia |= a.is_int(n);
m_lra |= a.is_real(n);
if (!is_app(n)) {
return v;
}
if (ctx.e_internalized(n)) {
e = ctx.get_enode(n);
v = e->get_th_var(get_id());
}
else {
ctx.internalize(n, false);
e = ctx.get_enode(n);
}
if (v == null_theory_var) {
v = mk_var(e);
}
if (is_interpreted(to_app(n))) {
found_non_utvpi_expr(n);
}
return v;
}
template<typename Ext>
void theory_utvpi<Ext>::reset_eh() {
m_graph .reset();
m_zero_int = null_theory_var;
m_zero_real = null_theory_var;
m_atoms .reset();
m_asserted_atoms .reset();
m_stats .reset();
m_scopes .reset();
m_asserted_qhead = 0;
m_agility = 0.5;
m_lia = false;
m_lra = false;
m_non_utvpi_exprs = false;
theory::reset_eh();
}
template<typename Ext>
void theory_utvpi<Ext>::new_eq_or_diseq(bool is_eq, th_var v1, th_var v2, justification& eq_just) {
rational k;
th_var s = expand(true, v1, k);
th_var t = expand(false, v2, k);
context& ctx = get_context();
ast_manager& m = get_manager();
if (s == t) {
if (is_eq != k.is_zero()) {
// conflict 0 /= k;
inc_conflicts();
ctx.set_conflict(&eq_just);
}
}
else {
//
// Create equality ast, internalize_atom
// assign the corresponding equality literal.
//
app_ref eq(m), s2(m), t2(m);
app* s1 = get_enode(s)->get_owner();
app* t1 = get_enode(t)->get_owner();
s2 = a.mk_sub(t1, s1);
t2 = a.mk_numeral(k, m.get_sort(s2.get()));
// t1 - s1 = k
eq = m.mk_eq(s2.get(), t2.get());
TRACE("utvpi",
tout << v1 << " .. " << v2 << "\n";
tout << mk_pp(eq.get(), m) <<"\n";);
if (!internalize_atom(eq.get(), false)) {
UNREACHABLE();
}
literal l(ctx.get_literal(eq.get()));
if (!is_eq) {
l = ~l;
}
ctx.assign(l, b_justification(&eq_just), false);
}
}
template<typename Ext>
void theory_utvpi<Ext>::inc_conflicts() {
m_stats.m_num_conflicts++;
if (m_params.m_arith_adaptive) {
double g = m_params.m_arith_adaptive_propagation_threshold;
m_agility = m_agility*g + 1 - g;
}
}
template<typename Ext>
void theory_utvpi<Ext>::set_conflict() {
inc_conflicts();
literal_vector const& lits = m_nc_functor.get_lits();
context & ctx = get_context();
TRACE("utvpi",
tout << "conflict: ";
for (unsigned i = 0; i < lits.size(); ++i) {
ctx.display_literal_info(tout, lits[i]);
}
tout << "\n";
);
if (m_params.m_arith_dump_lemmas) {
char const * logic = m_lra ? (m_lia?"QF_LIRA":"QF_LRA") : "QF_LIA";
ctx.display_lemma_as_smt_problem(lits.size(), lits.c_ptr(), false_literal, logic);
}
vector<parameter> params;
if (get_manager().proofs_enabled()) {
params.push_back(parameter(symbol("farkas")));
params.resize(lits.size()+1, parameter(rational(1)));
}
ctx.set_conflict(
ctx.mk_justification(
ext_theory_conflict_justification(
get_id(), ctx.get_region(),
lits.size(), lits.c_ptr(), 0, 0, params.size(), params.c_ptr())));
m_nc_functor.reset();
}
template<typename Ext>
void theory_utvpi<Ext>::found_non_utvpi_expr(expr* n) {
if (!m_non_utvpi_exprs) {
TRACE("utvpi", tout << "found non horn logic expression:\n" << mk_pp(n, get_manager()) << "\n";);
get_context().push_trail(value_trail<context, bool>(m_non_utvpi_exprs));
m_non_utvpi_exprs = true;
}
}
template<typename Ext>
void theory_utvpi<Ext>::init(context* ctx) {
theory::init(ctx);
m_zero_int = mk_var(ctx->mk_enode(a.mk_numeral(rational(0), true), false, false, true));
m_zero_real = mk_var(ctx->mk_enode(a.mk_numeral(rational(0), false), false, false, true));
}
/**
\brief Create negated literal.
The negation of: E <= 0
-E + epsilon <= 0
or
-E + 1 <= 0
*/
template<typename Ext>
void theory_utvpi<Ext>::negate(coeffs& coeffs, rational& weight) {
for (unsigned i = 0; i < coeffs.size(); ++i) {
coeffs[i].second.neg();
}
weight.neg();
}
template<typename Ext>
typename theory_utvpi<Ext>::numeral theory_utvpi<Ext>::mk_weight(bool is_real, bool is_strict, rational const& w) const {
if (is_strict) {
return numeral(w) + (is_real?m_epsilon:numeral(1));
}
else {
return numeral(w);
}
}
template<typename Ext>
void theory_utvpi<Ext>::mk_coeffs(vector<std::pair<expr*,rational> > const& terms, coeffs& coeffs, rational& w) {
coeffs.reset();
w = m_test.get_weight();
for (unsigned i = 0; i < terms.size(); ++i) {
coeffs.push_back(std::make_pair(mk_var(terms[i].first), terms[i].second));
}
}
template<typename Ext>
bool theory_utvpi<Ext>::internalize_atom(app * n, bool) {
context & ctx = get_context();
if (!a.is_le(n) && !a.is_ge(n) && !a.is_lt(n) && !a.is_gt(n)) {
found_non_utvpi_expr(n);
return false;
}
SASSERT(!ctx.b_internalized(n));
expr* e1 = n->get_arg(0), *e2 = n->get_arg(1);
if (a.is_ge(n) || a.is_gt(n)) {
std::swap(e1, e2);
}
bool is_strict = a.is_gt(n) || a.is_lt(n);
bool cl = m_test.linearize(e1, e2);
if (!cl) {
found_non_utvpi_expr(n);
return false;
}
rational w;
coeffs coeffs;
mk_coeffs(m_test.get_linearization(), coeffs, w);
bool_var bv = ctx.mk_bool_var(n);
ctx.set_var_theory(bv, get_id());
literal l(bv);
numeral w1 = mk_weight(a.is_real(e1), is_strict, w);
edge_id pos = add_ineq(coeffs, w1, l);
negate(coeffs, w);
numeral w2 = mk_weight(a.is_real(e1), !is_strict, w);
edge_id neg = add_ineq(coeffs, w2, ~l);
m_bool_var2atom.insert(bv, m_atoms.size());
m_atoms.push_back(atom(bv, pos, neg));
TRACE("utvpi",
tout << mk_pp(n, get_manager()) << "\n";
m_graph.display_edge(tout << "pos: ", pos);
m_graph.display_edge(tout << "neg: ", neg);
);
return true;
}
template<typename Ext>
bool theory_utvpi<Ext>::internalize_term(app * term) {
bool result = null_theory_var != mk_term(term);
CTRACE("utvpi", !result, tout << "Did not internalize " << mk_pp(term, get_manager()) << "\n";);
TRACE("utvpi", tout << "Terms may not be internalized " << mk_pp(term, get_manager()) << "\n";);
found_non_utvpi_expr(term);
return result;
}
template<typename Ext>
void theory_utvpi<Ext>::assign_eh(bool_var v, bool is_true) {
m_stats.m_num_assertions++;
unsigned idx = m_bool_var2atom.find(v);
SASSERT(get_context().get_assignment(v) != l_undef);
SASSERT((get_context().get_assignment(v) == l_true) == is_true);
m_atoms[idx].assign_eh(is_true);
m_asserted_atoms.push_back(idx);
}
template<typename Ext>
void theory_utvpi<Ext>::push_scope_eh() {
theory::push_scope_eh();
m_graph.push();
m_scopes.push_back(scope());
scope & s = m_scopes.back();
s.m_atoms_lim = m_atoms.size();
s.m_asserted_atoms_lim = m_asserted_atoms.size();
s.m_asserted_qhead_old = m_asserted_qhead;
}
template<typename Ext>
void theory_utvpi<Ext>::pop_scope_eh(unsigned num_scopes) {
unsigned lvl = m_scopes.size();
SASSERT(num_scopes <= lvl);
unsigned new_lvl = lvl - num_scopes;
scope & s = m_scopes[new_lvl];
del_atoms(s.m_atoms_lim);
m_asserted_atoms.shrink(s.m_asserted_atoms_lim);
m_asserted_qhead = s.m_asserted_qhead_old;
m_scopes.shrink(new_lvl);
m_graph.pop(num_scopes);
theory::pop_scope_eh(num_scopes);
}
template<typename Ext>
final_check_status theory_utvpi<Ext>::final_check_eh() {
SASSERT(is_consistent());
TRACE("utvpi", display(tout););
if (can_propagate()) {
propagate();
return FC_CONTINUE;
}
else if (!check_z_consistency()) {
return FC_CONTINUE;
}
else if (m_non_utvpi_exprs) {
return FC_GIVEUP;
}
else {
m_graph.set_to_zero(to_var(m_zero_int), to_var(m_zero_real));
m_graph.set_to_zero(neg(to_var(m_zero_int)), neg(to_var(m_zero_real)));
m_graph.set_to_zero(to_var(m_zero_int), neg(to_var(m_zero_int)));
return FC_DONE;
}
}
template<typename Ext>
bool theory_utvpi<Ext>::check_z_consistency() {
int_vector scc_id;
m_graph.compute_zero_edge_scc(scc_id);
unsigned sz = get_num_vars();
for (unsigned i = 0; i < sz; ++i) {
enode* e = get_enode(i);
if (a.is_int(e->get_owner())) {
continue;
}
th_var v1 = to_var(i);
th_var v2 = neg(v1);
rational r1 = m_graph.get_assignment(v1).get_rational();
rational r2 = m_graph.get_assignment(v2).get_rational();
SASSERT(r1.is_int());
SASSERT(r2.is_int());
if (r1.is_even() == r2.is_even()) {
continue;
}
if (scc_id[v1] != scc_id[v2]) {
continue;
}
if (scc_id[v1] == -1) {
continue;
}
// they are in the same SCC and have different parities => contradiction.
m_nc_functor.reset();
VERIFY(m_graph.find_shortest_zero_edge_path(v1, v2, UINT_MAX, m_nc_functor));
VERIFY(m_graph.find_shortest_zero_edge_path(v2, v1, UINT_MAX, m_nc_functor));
set_conflict();
return false;
}
return true;
}
template<typename Ext>
void theory_utvpi<Ext>::display(std::ostream& out) const {
for (unsigned i = 0; i < m_atoms.size(); ++i) {
m_atoms[i].display(*this, out); out << "\n";
}
m_graph.display(out);
}
template<typename Ext>
void theory_utvpi<Ext>::collect_statistics(::statistics& st) const {
st.update("utvpi conflicts", m_stats.m_num_conflicts);
st.update("utvpi assignments", m_stats.m_num_assertions);
m_arith_eq_adapter.collect_statistics(st);
m_graph.collect_statistics(st);
}
template<typename Ext>
void theory_utvpi<Ext>::del_atoms(unsigned old_size) {
typename atoms::iterator begin = m_atoms.begin() + old_size;
typename atoms::iterator it = m_atoms.end();
while (it != begin) {
--it;
m_bool_var2atom.erase(it->get_bool_var());
}
m_atoms.shrink(old_size);
}
template<typename Ext>
void theory_utvpi<Ext>::propagate() {
bool consistent = true;
while (consistent && can_propagate()) {
unsigned idx = m_asserted_atoms[m_asserted_qhead];
m_asserted_qhead++;
consistent = propagate_atom(m_atoms[idx]);
}
}
template<typename Ext>
bool theory_utvpi<Ext>::propagate_atom(atom const& a) {
context& ctx = get_context();
TRACE("utvpi", a.display(*this, tout); tout << "\n";);
if (ctx.inconsistent()) {
return false;
}
int edge_id = a.get_asserted_edge();
if (!enable_edge(edge_id)) {
m_graph.traverse_neg_cycle2(m_params.m_arith_stronger_lemmas, m_nc_functor);
set_conflict();
return false;
}
return true;
}
template<typename Ext>
theory_var theory_utvpi<Ext>::mk_term(app* n) {
context& ctx = get_context();
bool cl = m_test.linearize(n);
if (!cl) {
found_non_utvpi_expr(n);
return null_theory_var;
}
coeffs coeffs;
rational w;
mk_coeffs(m_test.get_linearization(), coeffs, w);
if (coeffs.empty()) {
return mk_num(n, w);
}
if (coeffs.size() == 1 && coeffs[0].second.is_one()) {
return coeffs[0].first;
}
th_var target = mk_var(ctx.mk_enode(n, false, false, true));
coeffs.push_back(std::make_pair(target, rational(-1)));
VERIFY(enable_edge(add_ineq(coeffs, numeral(w), null_literal)));
negate(coeffs, w);
VERIFY(enable_edge(add_ineq(coeffs, numeral(w), null_literal)));
return target;
}
template<typename Ext>
theory_var theory_utvpi<Ext>::mk_num(app* n, rational const& r) {
theory_var v = null_theory_var;
context& ctx = get_context();
if (r.is_zero()) {
v = a.is_int(n)?m_zero_int:m_zero_real;
}
else if (ctx.e_internalized(n)) {
enode* e = ctx.get_enode(n);
v = e->get_th_var(get_id());
SASSERT(v != null_theory_var);
}
else {
v = mk_var(ctx.mk_enode(n, false, false, true));
// v = k: v <= k k <= v
coeffs coeffs;
coeffs.push_back(std::make_pair(v, rational(1)));
VERIFY(enable_edge(add_ineq(coeffs, numeral(r), null_literal)));
coeffs.back().second.neg();
VERIFY(enable_edge(add_ineq(coeffs, numeral(-r), null_literal)));
}
return v;
}
template<typename Ext>
theory_var theory_utvpi<Ext>::expand(bool pos, th_var v, rational & k) {
context& ctx = get_context();
enode* e = get_enode(v);
expr* x, *y;
rational r;
for (;;) {
app* n = e->get_owner();
if (a.is_add(n, x, y)) {
if (a.is_numeral(x, r)) {
e = ctx.get_enode(y);
}
else if (a.is_numeral(y, r)) {
e = ctx.get_enode(x);
}
v = e->get_th_var(get_id());
SASSERT(v != null_theory_var);
if (v == null_theory_var) {
break;
}
if (pos) {
k += r;
}
else {
k -= r;
}
}
else {
break;
}
}
return v;
}
// m_graph(source, target, weight, ex);
// target - source <= weight
template<typename Ext>
edge_id theory_utvpi<Ext>::add_ineq(vector<std::pair<th_var, rational> > const& terms, numeral const& weight, literal l) {
SASSERT(terms.size() <= 2);
SASSERT(terms.size() < 1 || terms[0].second.is_one() || terms[0].second.is_minus_one());
SASSERT(terms.size() < 2 || terms[1].second.is_one() || terms[1].second.is_minus_one());
th_var v1 = null_theory_var, v2 = null_theory_var;
bool pos1 = true, pos2 = true;
if (terms.size() >= 1) {
v1 = terms[0].first;
pos1 = terms[0].second.is_one();
SASSERT(v1 != null_theory_var);
SASSERT(pos1 || terms[0].second.is_minus_one());
}
if (terms.size() >= 2) {
v2 = terms[1].first;
pos2 = terms[1].second.is_one();
SASSERT(v1 != null_theory_var);
SASSERT(pos2 || terms[1].second.is_minus_one());
}
// TRACE("utvpi", tout << (pos1?"$":"-$") << v1 << (pos2?" + $":" - $") << v2 << " + " << weight << " <= 0\n";);
edge_id id = m_graph.get_num_edges();
th_var w1 = to_var(v1), w2 = to_var(v2);
if (terms.size() == 1 && pos1) {
m_graph.add_edge(neg(w1), pos(w1), -weight-weight, l);
m_graph.add_edge(neg(w1), pos(w1), -weight-weight, l);
}
else if (terms.size() == 1 && !pos1) {
m_graph.add_edge(pos(w1), neg(w1), -weight-weight, l);
m_graph.add_edge(pos(w1), neg(w1), -weight-weight, l);
}
else if (pos1 && pos2) {
m_graph.add_edge(neg(w2), pos(w1), -weight, l);
m_graph.add_edge(neg(w1), pos(w2), -weight, l);
}
else if (pos1 && !pos2) {
m_graph.add_edge(pos(w2), pos(w1), -weight, l);
m_graph.add_edge(neg(w1), neg(w2), -weight, l);
}
else if (!pos1 && pos2) {
m_graph.add_edge(neg(w2), neg(w1), -weight, l);
m_graph.add_edge(pos(w1), pos(w2), -weight, l);
}
else {
m_graph.add_edge(pos(w1), neg(w2), -weight, l);
m_graph.add_edge(pos(w2), neg(w1), -weight, l);
}
return id;
}
template<typename Ext>
bool theory_utvpi<Ext>::enable_edge(edge_id id) {
return (id == null_edge_id) || (m_graph.enable_edge(id) && m_graph.enable_edge(id+1));
}
template<typename Ext>
bool theory_utvpi<Ext>::is_consistent() const {
return m_graph.is_feasible();
}
// models:
template<typename Ext>
void theory_utvpi<Ext>::init_model(model_generator & m) {
m_factory = alloc(arith_factory, get_manager());
m.register_factory(m_factory);
// TBD: enforce strong or tight coherence?
compute_delta();
}
template<typename Ext>
model_value_proc * theory_utvpi<Ext>::mk_value(enode * n, model_generator & mg) {
theory_var v = n->get_th_var(get_id());
SASSERT(v != null_theory_var);
numeral val1 = m_graph.get_assignment(to_var(v));
numeral val2 = m_graph.get_assignment(neg(to_var(v)));
numeral val = val1 - val2;
rational num = val.get_rational() + (m_delta * val.get_infinitesimal().to_rational());
num = num/rational(2);
TRACE("utvpi", tout << mk_pp(n->get_owner(), get_manager()) << " |-> " << num << "\n";);
return alloc(expr_wrapper_proc, m_factory->mk_value(num, a.is_int(n->get_owner())));
}
/**
\brief Compute numeral values for the infinitesimals to satisfy the inequalities.
*/
template<typename Ext>
void theory_utvpi<Ext>::compute_delta() {
m_delta = rational(1);
unsigned sz = m_graph.get_num_edges();
for (unsigned i = 0; i < sz; ++i) {
if (!m_graph.is_enabled(i)) {
continue;
}
numeral w = m_graph.get_weight(i);
numeral tgt = m_graph.get_assignment(m_graph.get_target(i));
numeral src = m_graph.get_assignment(m_graph.get_source(i));
numeral b = tgt - src - w;
SASSERT(b.is_nonpos());
rational eps_r = b.get_infinitesimal();
// Given: b <= 0
// suppose that 0 < b.eps
// then we have 0 > b.num
// then delta must ensure:
// 0 >= b.num + delta*b.eps
// <=>
// -b.num/b.eps >= delta
if (eps_r.is_pos()) {
rational num_r = -b.get_rational();
SASSERT(num_r.is_pos());
rational new_delta = num_r/eps_r;
if (new_delta < m_delta) {
m_delta = new_delta;
}
}
}
}
};
#endif