/*++ Copyright (c) 2016 Microsoft Corporation Module Name: theory_lra.cpp Abstract: Author: Lev Nachmanson (levnach) 2016-25-3 Nikolaj Bjorner (nbjorner) Revision History: --*/ #include "util/stopwatch.h" #include "util/lp/lp_solver.h" #include "util/lp/lp_primal_simplex.h" #include "util/lp/lp_dual_simplex.h" #include "util/lp/indexed_value.h" #include "util/lp/lar_solver.h" #include "util/nat_set.h" #include "util/optional.h" #include "util/lp/lp_params.hpp" #include "util/inf_rational.h" #include "util/cancel_eh.h" #include "util/scoped_timer.h" #include "util/nat_set.h" #include "util/lp/nra_solver.h" #include "ast/ast_pp.h" #include "model/numeral_factory.h" #include "smt/smt_theory.h" #include "smt/smt_context.h" #include "smt/theory_lra.h" #include "smt/smt_model_generator.h" #include "smt/arith_eq_adapter.h" #include "tactic/generic_model_converter.h" #include "math/polynomial/algebraic_numbers.h" #include "math/polynomial/polynomial.h" namespace lp_api { enum bound_kind { lower_t, upper_t }; std::ostream& operator<<(std::ostream& out, bound_kind const& k) { switch (k) { case lower_t: return out << "<="; case upper_t: return out << ">="; } return out; } class bound { smt::bool_var m_bv; smt::theory_var m_var; bool m_is_int; rational m_value; bound_kind m_bound_kind; public: bound(smt::bool_var bv, smt::theory_var v, bool is_int, rational const & val, bound_kind k): m_bv(bv), m_var(v), m_is_int(is_int), m_value(val), m_bound_kind(k) { } virtual ~bound() {} smt::theory_var get_var() const { return m_var; } smt::bool_var get_bv() const { return m_bv; } bound_kind get_bound_kind() const { return m_bound_kind; } bool is_int() const { return m_is_int; } rational const& get_value() const { return m_value; } inf_rational get_value(bool is_true) const { if (is_true) return inf_rational(m_value); // v >= value or v <= value if (m_is_int) { SASSERT(m_value.is_int()); if (m_bound_kind == lower_t) return inf_rational(m_value - rational::one()); // v <= value - 1 return inf_rational(m_value + rational::one()); // v >= value + 1 } else { if (m_bound_kind == lower_t) return inf_rational(m_value, false); // v <= value - epsilon return inf_rational(m_value, true); // v >= value + epsilon } } virtual std::ostream& display(std::ostream& out) const { return out << m_value << " " << get_bound_kind() << " v" << get_var(); } }; std::ostream& operator<<(std::ostream& out, bound const& b) { return b.display(out); } struct stats { unsigned m_assert_lower; unsigned m_assert_upper; unsigned m_add_rows; unsigned m_bounds_propagations; unsigned m_num_iterations; unsigned m_num_iterations_with_no_progress; unsigned m_need_to_solve_inf; unsigned m_fixed_eqs; unsigned m_conflicts; unsigned m_bound_propagations1; unsigned m_bound_propagations2; unsigned m_assert_diseq; unsigned m_gomory_cuts; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; typedef optional opt_inf_rational; } namespace smt { typedef ptr_vector lp_bounds; class theory_lra::imp { struct scope { unsigned m_bounds_lim; unsigned m_idiv_lim; unsigned m_asserted_qhead; unsigned m_asserted_atoms_lim; unsigned m_underspecified_lim; unsigned m_var_trail_lim; expr* m_not_handled; }; struct delayed_atom { unsigned m_bv; bool m_is_true; delayed_atom(unsigned b, bool t): m_bv(b), m_is_true(t) {} }; class resource_limit : public lp::lp_resource_limit { imp& m_imp; public: resource_limit(imp& i): m_imp(i) { } bool get_cancel_flag() override { return m_imp.m.canceled(); } }; theory_lra& th; ast_manager& m; theory_arith_params& m_arith_params; arith_util a; bool m_has_int; arith_eq_adapter m_arith_eq_adapter; vector m_columns; // temporary values kept during internalization struct internalize_state { expr_ref_vector m_terms; vector m_coeffs; svector m_vars; rational m_offset; ptr_vector m_terms_to_internalize; internalize_state(ast_manager& m): m_terms(m) {} void reset() { m_terms.reset(); m_coeffs.reset(); m_offset.reset(); m_vars.reset(); m_terms_to_internalize.reset(); } }; ptr_vector m_internalize_states; unsigned m_internalize_head; class scoped_internalize_state { imp& m_imp; internalize_state& m_st; internalize_state& push_internalize(imp& i) { if (i.m_internalize_head == i.m_internalize_states.size()) { i.m_internalize_states.push_back(alloc(internalize_state, i.m)); } internalize_state& st = *i.m_internalize_states[i.m_internalize_head++]; st.reset(); return st; } public: scoped_internalize_state(imp& i): m_imp(i), m_st(push_internalize(i)) {} ~scoped_internalize_state() { --m_imp.m_internalize_head; } expr_ref_vector& terms() { return m_st.m_terms; } vector& coeffs() { return m_st.m_coeffs; } svector& vars() { return m_st.m_vars; } rational& offset() { return m_st.m_offset; } ptr_vector& terms_to_internalize() { return m_st.m_terms_to_internalize; } void push(expr* e, rational c) { m_st.m_terms.push_back(e); m_st.m_coeffs.push_back(c); } void set_back(unsigned i) { if (terms().size() == i + 1) return; terms()[i] = terms().back(); coeffs()[i] = coeffs().back(); terms().pop_back(); coeffs().pop_back(); } }; typedef vector> var_coeffs; svector m_theory_var2var_index; // translate from theory variables to lar vars svector m_var_index2theory_var; // reverse map from lp_solver variables to theory variables svector m_term_index2theory_var; // reverse map from lp_solver variables to theory variables var_coeffs m_left_side; // constraint left side mutable std::unordered_map m_variable_values; // current model lp::var_index m_one_var; lp::var_index m_zero_var; lp::var_index m_rone_var; lp::var_index m_rzero_var; enum constraint_source { inequality_source, equality_source, definition_source, null_source }; svector m_constraint_sources; svector m_inequalities; // asserted rows corresponding to inequality literals. svector m_equalities; // asserted rows corresponding to equalities. svector m_definitions; // asserted rows corresponding to definitions svector m_asserted_atoms; expr* m_not_handled; ptr_vector m_underspecified; ptr_vector m_idiv_terms; unsigned_vector m_var_trail; vector > m_use_list; // bounds where variables are used. // attributes for incremental version: u_map m_bool_var2bound; vector m_bounds; unsigned_vector m_unassigned_bounds; unsigned_vector m_bounds_trail; unsigned m_asserted_qhead; svector m_to_check; // rows that should be checked for theory propagation svector > m_assume_eq_candidates; unsigned m_assume_eq_head; unsigned m_num_conflicts; // non-linear arithmetic scoped_ptr m_nra; bool m_use_nra_model; scoped_ptr m_a1, m_a2; // integer arithmetic scoped_ptr m_lia; struct var_value_eq { imp & m_th; var_value_eq(imp & th):m_th(th) {} bool operator()(theory_var v1, theory_var v2) const { if (m_th.is_int(v1) != m_th.is_int(v2)) { return false; } return m_th.is_eq(v1, v2); } }; struct var_value_hash { imp & m_th; var_value_hash(imp & th):m_th(th) {} unsigned operator()(theory_var v) const { if (m_th.m_use_nra_model) { return m_th.is_int(v); } else { return (unsigned)std::hash()(m_th.get_value(v)); } } }; int_hashtable m_model_eqs; svector m_scopes; lp_api::stats m_stats; arith_factory* m_factory; scoped_ptr m_solver; resource_limit m_resource_limit; lp_bounds m_new_bounds; context& ctx() const { return th.get_context(); } theory_id get_id() const { return th.get_id(); } bool is_int(theory_var v) const { return is_int(get_enode(v)); } bool is_int(enode* n) const { return a.is_int(n->get_owner()); } enode* get_enode(theory_var v) const { return th.get_enode(v); } enode* get_enode(expr* e) const { return ctx().get_enode(e); } expr* get_owner(theory_var v) const { return get_enode(v)->get_owner(); } void init_solver() { if (m_solver) return; reset_variable_values(); m_theory_var2var_index.reset(); m_solver = alloc(lp::lar_solver); lp_params lp(ctx().get_params()); m_solver->settings().set_resource_limit(m_resource_limit); m_solver->settings().simplex_strategy() = static_cast(lp.simplex_strategy()); m_solver->settings().bound_propagation() = BP_NONE != propagation_mode(); m_solver->settings().m_enable_hnf = lp.enable_hnf(); m_solver->set_track_pivoted_rows(lp.bprop_on_pivoted_rows()); // todo : do not use m_arith_branch_cut_ratio for deciding on cheap cuts unsigned branch_cut_ratio = ctx().get_fparams().m_arith_branch_cut_ratio; m_solver->set_cut_strategy(branch_cut_ratio); m_solver->settings().m_int_run_gcd_test = ctx().get_fparams().m_arith_gcd_test; m_solver->settings().set_random_seed(ctx().get_fparams().m_random_seed); m_lia = alloc(lp::int_solver, m_solver.get()); get_one(true); get_zero(true); get_one(false); get_zero(false); } void ensure_nra() { if (!m_nra) { m_nra = alloc(nra::solver, *m_solver.get(), m.limit(), ctx().get_params()); for (auto const& _s : m_scopes) { (void)_s; m_nra->push(); } } } lp::var_index add_const(int c, lp::var_index& var, bool is_int) { if (var != UINT_MAX) { return var; } app_ref cnst(a.mk_numeral(rational(c), is_int), m); TRACE("arith", tout << "add " << cnst << "\n";); mk_enode(cnst); theory_var v = mk_var(cnst); var = m_solver->add_var(v, true); m_theory_var2var_index.setx(v, var, UINT_MAX); m_var_index2theory_var.setx(var, v, UINT_MAX); m_var_trail.push_back(v); add_def_constraint(m_solver->add_var_bound(var, lp::GE, rational(c))); add_def_constraint(m_solver->add_var_bound(var, lp::LE, rational(c))); return var; } lp::var_index get_one(bool is_int) { return add_const(1, is_int ? m_one_var : m_rone_var, is_int); } lp::var_index get_zero(bool is_int) { return add_const(0, is_int ? m_zero_var : m_rzero_var, is_int); } void found_not_handled(expr* n) { m_not_handled = n; if (is_app(n) && is_underspecified(to_app(n))) { TRACE("arith", tout << "Unhandled: " << mk_pp(n, m) << "\n";); m_underspecified.push_back(to_app(n)); } } bool is_numeral(expr* term, rational& r) { rational mul(1); do { if (a.is_numeral(term, r)) { r *= mul; return true; } if (a.is_uminus(term, term)) { mul.neg(); continue; } if (a.is_to_real(term, term)) { continue; } return false; } while (false); return false; } void linearize_term(expr* term, scoped_internalize_state& st) { st.push(term, rational::one()); linearize(st); } void linearize_ineq(expr* lhs, expr* rhs, scoped_internalize_state& st) { st.push(lhs, rational::one()); st.push(rhs, rational::minus_one()); linearize(st); } void linearize(scoped_internalize_state& st) { expr_ref_vector & terms = st.terms(); svector& vars = st.vars(); vector& coeffs = st.coeffs(); rational& offset = st.offset(); rational r; expr* n1, *n2; unsigned index = 0; while (index < terms.size()) { SASSERT(index >= vars.size()); expr* n = terms[index].get(); st.terms_to_internalize().push_back(n); if (a.is_add(n)) { for (expr* arg : *to_app(n)) { st.push(arg, coeffs[index]); } st.set_back(index); } else if (a.is_sub(n)) { unsigned sz = to_app(n)->get_num_args(); terms[index] = to_app(n)->get_arg(0); for (unsigned i = 1; i < sz; ++i) { st.push(to_app(n)->get_arg(i), -coeffs[index]); } } else if (a.is_mul(n, n1, n2) && is_numeral(n1, r)) { coeffs[index] *= r; terms[index] = n2; st.terms_to_internalize().push_back(n1); } else if (a.is_mul(n, n1, n2) && is_numeral(n2, r)) { coeffs[index] *= r; terms[index] = n1; st.terms_to_internalize().push_back(n2); } else if (a.is_mul(n)) { theory_var v; internalize_mul(to_app(n), v, r); coeffs[index] *= r; coeffs[vars.size()] = coeffs[index]; vars.push_back(v); ++index; } else if (a.is_numeral(n, r)) { offset += coeffs[index]*r; ++index; } else if (a.is_uminus(n, n1)) { coeffs[index].neg(); terms[index] = n1; } else if (is_app(n) && a.get_family_id() == to_app(n)->get_family_id()) { bool is_first = !ctx().e_internalized(n); app* t = to_app(n); internalize_args(t); mk_enode(t); theory_var v = mk_var(n); coeffs[vars.size()] = coeffs[index]; vars.push_back(v); ++index; if (!is_first) { // skip recursive internalization } else if (a.is_to_int(n, n1)) { if (!ctx().relevancy()) mk_to_int_axiom(t); } else if (a.is_to_real(n, n1)) { theory_var v1 = mk_var(n1); internalize_eq(v, v1); } else if (a.is_idiv(n, n1, n2)) { if (!a.is_numeral(n2, r)) found_not_handled(n); m_idiv_terms.push_back(n); app * mod = a.mk_mod(n1, n2); ctx().internalize(mod, false); if (ctx().relevancy()) ctx().add_relevancy_dependency(n, mod); } else if (a.is_mod(n, n1, n2)) { if (!ctx().relevancy()) mk_idiv_mod_axioms(n1, n2); } else if (a.is_rem(n, n1, n2)) { if (!a.is_numeral(n2, r)) found_not_handled(n); if (!ctx().relevancy()) mk_rem_axiom(n1, n2); } else if (a.is_div(n, n1, n2)) { if (!a.is_numeral(n2, r)) found_not_handled(n); if (!ctx().relevancy()) mk_div_axiom(n1, n2); } else if (a.is_power(n)) { found_not_handled(n); } else { found_not_handled(n); } } else { if (is_app(n)) { internalize_args(to_app(n)); } theory_var v = mk_var(n); coeffs[vars.size()] = coeffs[index]; vars.push_back(v); ++index; } } for (unsigned i = st.terms_to_internalize().size(); i-- > 0; ) { expr* n = st.terms_to_internalize()[i]; if (is_app(n)) { mk_enode(to_app(n)); } } st.terms_to_internalize().reset(); } void internalize_args(app* t) { for (unsigned i = 0; reflect(t) && i < t->get_num_args(); ++i) { if (!ctx().e_internalized(t->get_arg(i))) { ctx().internalize(t->get_arg(i), false); } } } void internalize_mul(app* t, theory_var& v, rational& r) { SASSERT(a.is_mul(t)); bool _has_var = has_var(t); if (!_has_var) { internalize_args(t); mk_enode(t); } r = rational::one(); rational r1; v = mk_var(t); svector vars; ptr_buffer todo; todo.push_back(t); while (!todo.empty()) { expr* n = todo.back(); todo.pop_back(); if (a.is_mul(n)) { for (expr* arg : *to_app(n)) { todo.push_back(arg); } } else if (a.is_numeral(n, r1)) { r *= r1; } else { if (!ctx().e_internalized(n)) { ctx().internalize(n, false); } vars.push_back(get_var_index(mk_var(n))); } } TRACE("arith", tout << "v" << v << "(" << get_var_index(v) << ") := " << mk_pp(t, m) << " " << _has_var << "\n";); if (!_has_var) { ensure_nra(); m_nra->add_monomial(get_var_index(v), vars.size(), vars.c_ptr()); } } enode * mk_enode(app * n) { TRACE("arith", tout << expr_ref(n, m) << "\n";); if (ctx().e_internalized(n)) { return get_enode(n); } else { return ctx().mk_enode(n, !reflect(n), false, enable_cgc_for(n)); } } bool enable_cgc_for(app * n) const { // Congruence closure is not enabled for (+ ...) and (* ...) applications. return !(n->get_family_id() == get_id() && (n->get_decl_kind() == OP_ADD || n->get_decl_kind() == OP_MUL)); } void mk_clause(literal l1, literal l2, unsigned num_params, parameter * params) { TRACE("arith", literal lits[2]; lits[0] = l1; lits[1] = l2; ctx().display_literals_verbose(tout, 2, lits); tout << "\n";); ctx().mk_th_axiom(get_id(), l1, l2, num_params, params); } void mk_clause(literal l1, literal l2, literal l3, unsigned num_params, parameter * params) { TRACE("arith", literal lits[3]; lits[0] = l1; lits[1] = l2; lits[2] = l3; ctx().display_literals_verbose(tout, 3, lits); tout << "\n";); ctx().mk_th_axiom(get_id(), l1, l2, l3, num_params, params); } bool is_underspecified(app* n) const { if (n->get_family_id() == get_id()) { switch (n->get_decl_kind()) { case OP_DIV: case OP_IDIV: case OP_REM: case OP_MOD: return true; default: break; } } return false; } bool reflect(app* n) const { return m_arith_params.m_arith_reflect || is_underspecified(n); } bool has_var(expr* n) { if (!ctx().e_internalized(n)) { return false; } enode* e = get_enode(n); return th.is_attached_to_var(e); } theory_var mk_var(expr* n, bool internalize = true) { if (!ctx().e_internalized(n)) { ctx().internalize(n, false); } enode* e = get_enode(n); theory_var v; if (!th.is_attached_to_var(e)) { v = th.mk_var(e); SASSERT(m_bounds.size() <= static_cast(v) || m_bounds[v].empty()); if (m_bounds.size() <= static_cast(v)) { m_bounds.push_back(lp_bounds()); m_unassigned_bounds.push_back(0); } ctx().attach_th_var(e, &th, v); } else { v = e->get_th_var(get_id()); } SASSERT(null_theory_var != v); return v; } lp::var_index get_var_index(theory_var v) { lp::var_index result = UINT_MAX; if (m_theory_var2var_index.size() > static_cast(v)) { result = m_theory_var2var_index[v]; } if (result == UINT_MAX) { result = m_solver->add_var(v, is_int(v)); m_has_int |= is_int(v); m_theory_var2var_index.setx(v, result, UINT_MAX); m_var_index2theory_var.setx(result, v, UINT_MAX); m_var_trail.push_back(v); } return result; } void init_left_side(scoped_internalize_state& st) { SASSERT(all_zeros(m_columns)); svector const& vars = st.vars(); vector const& coeffs = st.coeffs(); for (unsigned i = 0; i < vars.size(); ++i) { theory_var var = vars[i]; rational const& coeff = coeffs[i]; if (m_columns.size() <= static_cast(var)) { m_columns.setx(var, coeff, rational::zero()); } else { m_columns[var] += coeff; } } m_left_side.clear(); // reset the coefficients after they have been used. for (unsigned i = 0; i < vars.size(); ++i) { theory_var var = vars[i]; rational const& r = m_columns[var]; if (!r.is_zero()) { m_left_side.push_back(std::make_pair(r, get_var_index(var))); m_columns[var].reset(); } } SASSERT(all_zeros(m_columns)); } bool all_zeros(vector const& v) const { for (rational const& r : v) { if (!r.is_zero()) { return false; } } return true; } void add_eq_constraint(lp::constraint_index index, enode* n1, enode* n2) { m_constraint_sources.setx(index, equality_source, null_source); m_equalities.setx(index, enode_pair(n1, n2), enode_pair(0, 0)); ++m_stats.m_add_rows; } void add_ineq_constraint(lp::constraint_index index, literal lit) { m_constraint_sources.setx(index, inequality_source, null_source); m_inequalities.setx(index, lit, null_literal); ++m_stats.m_add_rows; TRACE("arith", m_solver->print_constraint(index, tout) << "\n";); } void add_def_constraint(lp::constraint_index index) { m_constraint_sources.setx(index, definition_source, null_source); m_definitions.setx(index, null_theory_var, null_theory_var); ++m_stats.m_add_rows; } void add_def_constraint(lp::constraint_index index, theory_var v) { m_constraint_sources.setx(index, definition_source, null_source); m_definitions.setx(index, v, null_theory_var); ++m_stats.m_add_rows; } void internalize_eq(theory_var v1, theory_var v2) { app_ref term(m.mk_fresh_const("eq", a.mk_real()), m); scoped_internalize_state st(*this); st.vars().push_back(v1); st.vars().push_back(v2); st.coeffs().push_back(rational::one()); st.coeffs().push_back(rational::minus_one()); theory_var z = internalize_linearized_def(term, st); lp::var_index vi = get_var_index(z); add_def_constraint(m_solver->add_var_bound(vi, lp::LE, rational::zero())); add_def_constraint(m_solver->add_var_bound(vi, lp::GE, rational::zero())); TRACE("arith", { expr* o1 = get_enode(v1)->get_owner(); expr* o2 = get_enode(v2)->get_owner(); tout << "v" << v1 << " = " << "v" << v2 << ": " << mk_pp(o1, m) << " = " << mk_pp(o2, m) << "\n"; }); } void del_bounds(unsigned old_size) { for (unsigned i = m_bounds_trail.size(); i > old_size; ) { --i; unsigned v = m_bounds_trail[i]; lp_api::bound* b = m_bounds[v].back(); // del_use_lists(b); dealloc(b); m_bounds[v].pop_back(); } m_bounds_trail.shrink(old_size); } void updt_unassigned_bounds(theory_var v, int inc) { TRACE("arith", tout << "v" << v << " " << m_unassigned_bounds[v] << " += " << inc << "\n";); ctx().push_trail(vector_value_trail(m_unassigned_bounds, v)); m_unassigned_bounds[v] += inc; } bool is_unit_var(scoped_internalize_state& st) { return st.offset().is_zero() && st.vars().size() == 1 && st.coeffs()[0].is_one(); } bool is_one(scoped_internalize_state& st) { return st.offset().is_one() && st.vars().empty(); } bool is_zero(scoped_internalize_state& st) { return st.offset().is_zero() && st.vars().empty(); } theory_var internalize_def(app* term, scoped_internalize_state& st) { TRACE("arith", tout << expr_ref(term, m) << "\n";); if (ctx().e_internalized(term)) { IF_VERBOSE(0, verbose_stream() << "repeated term\n";); return mk_var(term, false); } linearize_term(term, st); if (is_unit_var(st)) { return st.vars()[0]; } else { theory_var v = mk_var(term); SASSERT(null_theory_var != v); st.coeffs().resize(st.vars().size() + 1); st.coeffs()[st.vars().size()] = rational::minus_one(); st.vars().push_back(v); return v; } } // term - v = 0 theory_var internalize_def(app* term) { scoped_internalize_state st(*this); linearize_term(term, st); return internalize_linearized_def(term, st); } theory_var internalize_linearized_def(app* term, scoped_internalize_state& st) { if (is_unit_var(st)) { return st.vars()[0]; } else if (is_one(st)) { return get_one(a.is_int(term)); } else if (is_zero(st)) { return get_zero(a.is_int(term)); } else { init_left_side(st); theory_var v = mk_var(term); lp::var_index vi = m_theory_var2var_index.get(v, UINT_MAX); TRACE("arith", tout << mk_pp(term, m) << " v" << v << "\n";); if (vi == UINT_MAX) { rational const& offset = st.offset(); if (!offset.is_zero()) { m_left_side.push_back(std::make_pair(offset, get_one(a.is_int(term)))); } SASSERT(!m_left_side.empty()); vi = m_solver->add_term(m_left_side); m_theory_var2var_index.setx(v, vi, UINT_MAX); if (m_solver->is_term(vi)) { m_term_index2theory_var.setx(m_solver->adjust_term_index(vi), v, UINT_MAX); } else { m_var_index2theory_var.setx(vi, v, UINT_MAX); } m_var_trail.push_back(v); TRACE("arith_verbose", tout << "v" << v << " := " << mk_pp(term, m) << " slack: " << vi << " scopes: " << m_scopes.size() << "\n"; m_solver->print_term(m_solver->get_term(vi), tout) << "\n";); } rational val; if (a.is_numeral(term, val)) { m_fixed_var_table.insert(value_sort_pair(val, is_int(v)), v); } return v; } } public: imp(theory_lra& th, ast_manager& m, theory_arith_params& ap): th(th), m(m), m_arith_params(ap), a(m), m_has_int(false), m_arith_eq_adapter(th, ap, a), m_internalize_head(0), m_one_var(UINT_MAX), m_zero_var(UINT_MAX), m_rone_var(UINT_MAX), m_rzero_var(UINT_MAX), m_not_handled(nullptr), m_asserted_qhead(0), m_assume_eq_head(0), m_num_conflicts(0), m_use_nra_model(false), m_model_eqs(DEFAULT_HASHTABLE_INITIAL_CAPACITY, var_value_hash(*this), var_value_eq(*this)), m_solver(nullptr), m_resource_limit(*this) { } ~imp() { del_bounds(0); std::for_each(m_internalize_states.begin(), m_internalize_states.end(), delete_proc()); } void init(context* ctx) { init_solver(); } void internalize_is_int(app * n) { SASSERT(a.is_is_int(n)); (void) mk_enode(n); if (!ctx().relevancy()) mk_is_int_axiom(n); } bool internalize_atom(app * atom, bool gate_ctx) { SASSERT(!ctx().b_internalized(atom)); bool_var bv = ctx().mk_bool_var(atom); ctx().set_var_theory(bv, get_id()); expr* n1, *n2; rational r; lp_api::bound_kind k; theory_var v = null_theory_var; if (a.is_le(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { v = internalize_def(to_app(n1)); k = lp_api::upper_t; } else if (a.is_ge(atom, n1, n2) && is_numeral(n2, r) && is_app(n1)) { v = internalize_def(to_app(n1)); k = lp_api::lower_t; } else if (a.is_is_int(atom)) { internalize_is_int(atom); return true; } else { TRACE("arith", tout << "Could not internalize " << mk_pp(atom, m) << "\n";); found_not_handled(atom); return true; } lp_api::bound* b = alloc(lp_api::bound, bv, v, is_int(v), r, k); m_bounds[v].push_back(b); updt_unassigned_bounds(v, +1); m_bounds_trail.push_back(v); m_bool_var2bound.insert(bv, b); TRACE("arith_verbose", tout << "Internalized " << mk_pp(atom, m) << "\n";); mk_bound_axioms(*b); //add_use_lists(b); return true; } bool internalize_term(app * term) { if (ctx().e_internalized(term) && th.is_attached_to_var(ctx().get_enode(term))) { // skip } else { internalize_def(term); } return true; } bool is_arith(enode* n) { return n && n->get_th_var(get_id()) != null_theory_var; } void internalize_eq_eh(app * atom, bool_var) { TRACE("arith_verbose", tout << mk_pp(atom, m) << "\n";); expr* lhs = nullptr, *rhs = nullptr; VERIFY(m.is_eq(atom, lhs, rhs)); enode * n1 = get_enode(lhs); enode * n2 = get_enode(rhs); if (is_arith(n1) && is_arith(n2) && n1 != n2) { m_arith_eq_adapter.mk_axioms(n1, n2); } } void assign_eh(bool_var v, bool is_true) { TRACE("arith", tout << mk_pp(ctx().bool_var2expr(v), m) << " " << (is_true?"true":"false") << "\n";); m_asserted_atoms.push_back(delayed_atom(v, is_true)); } lbool get_phase(bool_var v) { lp_api::bound* b; if (!m_bool_var2bound.find(v, b)) { return l_undef; } lp::lconstraint_kind k = lp::EQ; switch (b->get_bound_kind()) { case lp_api::lower_t: k = lp::GE; break; case lp_api::upper_t: k = lp::LE; break; default: break; } auto vi = get_var_index(b->get_var()); return m_solver->compare_values(vi, k, b->get_value()) ? l_true : l_false; } void new_eq_eh(theory_var v1, theory_var v2) { // or internalize_eq(v1, v2); m_arith_eq_adapter.new_eq_eh(v1, v2); } bool use_diseqs() const { return true; } void new_diseq_eh(theory_var v1, theory_var v2) { TRACE("arith", tout << "v" << v1 << " != " << "v" << v2 << "\n";); ++m_stats.m_assert_diseq; m_arith_eq_adapter.new_diseq_eh(v1, v2); } void push_scope_eh() { TRACE("arith", tout << "push\n";); m_scopes.push_back(scope()); scope& s = m_scopes.back(); s.m_bounds_lim = m_bounds_trail.size(); s.m_asserted_qhead = m_asserted_qhead; s.m_idiv_lim = m_idiv_terms.size(); s.m_asserted_atoms_lim = m_asserted_atoms.size(); s.m_not_handled = m_not_handled; s.m_underspecified_lim = m_underspecified.size(); s.m_var_trail_lim = m_var_trail.size(); m_solver->push(); if (m_nra) m_nra->push(); } void pop_scope_eh(unsigned num_scopes) { TRACE("arith", tout << "pop " << num_scopes << "\n";); if (num_scopes == 0) { return; } unsigned old_size = m_scopes.size() - num_scopes; del_bounds(m_scopes[old_size].m_bounds_lim); for (unsigned i = m_scopes[old_size].m_var_trail_lim; i < m_var_trail.size(); ++i) { lp::var_index vi = m_theory_var2var_index[m_var_trail[i]]; if (m_solver->is_term(vi)) { unsigned ti = m_solver->adjust_term_index(vi); m_term_index2theory_var[ti] = UINT_MAX; } else if (vi < m_var_index2theory_var.size()) { m_var_index2theory_var[vi] = UINT_MAX; } m_theory_var2var_index[m_var_trail[i]] = UINT_MAX; } m_idiv_terms.shrink(m_scopes[old_size].m_idiv_lim); m_asserted_atoms.shrink(m_scopes[old_size].m_asserted_atoms_lim); m_asserted_qhead = m_scopes[old_size].m_asserted_qhead; m_underspecified.shrink(m_scopes[old_size].m_underspecified_lim); m_var_trail.shrink(m_scopes[old_size].m_var_trail_lim); m_not_handled = m_scopes[old_size].m_not_handled; m_scopes.resize(old_size); m_solver->pop(num_scopes); // VERIFY(l_false != make_feasible()); m_new_bounds.reset(); m_to_check.reset(); if (m_nra) m_nra->pop(num_scopes); TRACE("arith", tout << "num scopes: " << num_scopes << " new scope level: " << m_scopes.size() << "\n";); } void restart_eh() { m_arith_eq_adapter.restart_eh(); } void relevant_eh(app* n) { TRACE("arith", tout << mk_pp(n, m) << "\n";); expr* n1, *n2; if (a.is_mod(n, n1, n2)) mk_idiv_mod_axioms(n1, n2); else if (a.is_rem(n, n1, n2)) mk_rem_axiom(n1, n2); else if (a.is_div(n, n1, n2)) mk_div_axiom(n1, n2); else if (a.is_to_int(n)) mk_to_int_axiom(n); else if (a.is_is_int(n)) mk_is_int_axiom(n); } // n < 0 || rem(a, n) = mod(a, n) // !n < 0 || rem(a, n) = -mod(a, n) void mk_rem_axiom(expr* dividend, expr* divisor) { expr_ref zero(a.mk_int(0), m); expr_ref rem(a.mk_rem(dividend, divisor), m); expr_ref mod(a.mk_mod(dividend, divisor), m); expr_ref mmod(a.mk_uminus(mod), m); expr_ref degz_expr(a.mk_ge(divisor, zero), m); literal dgez = mk_literal(degz_expr); literal pos = th.mk_eq(rem, mod, false); literal neg = th.mk_eq(rem, mmod, false); if (m.has_trace_stream()) { app_ref body(m); body = m.mk_ite(degz_expr, ctx().bool_var2expr(pos.var()), ctx().bool_var2expr(neg.var())); th.log_axiom_instantiation(body); } mk_axiom(~dgez, pos); mk_axiom( dgez, neg); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } // q = 0 or q * (p div q) = p void mk_div_axiom(expr* p, expr* q) { if (a.is_zero(q)) return; literal eqz = th.mk_eq(q, a.mk_real(0), false); literal eq = th.mk_eq(a.mk_mul(q, a.mk_div(p, q)), p, false); if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_not(ctx().bool_var2expr(eqz.var())), ctx().bool_var2expr(eq.var())); th.log_axiom_instantiation(body); } mk_axiom(eqz, eq); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } // to_int (to_real x) = x // to_real(to_int(x)) <= x < to_real(to_int(x)) + 1 void mk_to_int_axiom(app* n) { expr* x = nullptr, *y = nullptr; VERIFY (a.is_to_int(n, x)); if (a.is_to_real(x, y)) { if (m.has_trace_stream()) { app_ref body(m); body = m.mk_eq(n, y); th.log_axiom_instantiation(body); } mk_axiom(th.mk_eq(y, n, false)); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } else { expr_ref to_r(a.mk_to_real(n), m); expr_ref lo(a.mk_le(a.mk_sub(to_r, x), a.mk_real(0)), m); expr_ref hi(a.mk_ge(a.mk_sub(x, to_r), a.mk_real(1)), m); if (m.has_trace_stream()) th.log_axiom_instantiation(lo); mk_axiom(mk_literal(lo)); if (m.has_trace_stream()) { m.trace_stream() << "[end-of-instance]\n"; expr_ref body(m); body = m.mk_not(hi); th.log_axiom_instantiation(body); } mk_axiom(~mk_literal(hi)); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } } // is_int(x) <=> to_real(to_int(x)) = x void mk_is_int_axiom(app* n) { expr* x = nullptr; VERIFY(a.is_is_int(n, x)); literal eq = th.mk_eq(a.mk_to_real(a.mk_to_int(x)), x, false); literal is_int = ctx().get_literal(n); if (m.has_trace_stream()) { app_ref body(m); body = m.mk_iff(n, ctx().bool_var2expr(eq.var())); th.log_axiom_instantiation(body); } mk_axiom(~is_int, eq); mk_axiom(is_int, ~eq); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } void mk_idiv_mod_axioms(expr * p, expr * q) { if (a.is_zero(q)) { return; } TRACE("arith", tout << expr_ref(p, m) << " " << expr_ref(q, m) << "\n";); // if q is zero, then idiv and mod are uninterpreted functions. expr_ref div(a.mk_idiv(p, q), m); expr_ref mod(a.mk_mod(p, q), m); expr_ref zero(a.mk_int(0), m); literal eq = th.mk_eq(a.mk_add(a.mk_mul(q, div), mod), p, false); literal mod_ge_0 = mk_literal(a.mk_ge(mod, zero)); literal div_ge_0 = mk_literal(a.mk_ge(div, zero)); literal div_le_0 = mk_literal(a.mk_le(div, zero)); literal p_ge_0 = mk_literal(a.mk_ge(p, zero)); literal p_le_0 = mk_literal(a.mk_le(p, zero)); rational k(0); expr_ref upper(m); if (a.is_numeral(q, k)) { if (k.is_pos()) { upper = a.mk_numeral(k - 1, true); } else if (k.is_neg()) { upper = a.mk_numeral(-k - 1, true); } } else { k = rational::zero(); } context& c = ctx(); if (!k.is_zero()) { if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(eq.var())); th.log_axiom_instantiation(body); } mk_axiom(eq); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(mod_ge_0.var())); th.log_axiom_instantiation(body); } mk_axiom(mod_ge_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_not(m.mk_eq(q, zero)), a.mk_le(mod, upper)); th.log_axiom_instantiation(body); } mk_axiom(mk_literal(a.mk_le(mod, upper))); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (k.is_pos()) { if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_ge_0.var())); th.log_axiom_instantiation(body); } mk_axiom(~p_ge_0, div_ge_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_le_0.var())); th.log_axiom_instantiation(body); } mk_axiom(~p_le_0, div_le_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } else { if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_le_0.var())); th.log_axiom_instantiation(body); } mk_axiom(~p_ge_0, div_le_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_ge_0.var())); th.log_axiom_instantiation(body); } mk_axiom(~p_le_0, div_ge_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } } else { // q >= 0 or p = (p mod q) + q * (p div q) // q <= 0 or p = (p mod q) + q * (p div q) // q >= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) >= 0 // q <= 0 or (p mod q) < q // q >= 0 or (p mod q) < -q literal q_ge_0 = mk_literal(a.mk_ge(q, zero)); literal q_le_0 = mk_literal(a.mk_le(q, zero)); if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(eq.var())); th.log_axiom_instantiation(body); } mk_axiom(q_ge_0, eq); mk_axiom(q_le_0, eq); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_not(m.mk_eq(q, zero)), c.bool_var2expr(mod_ge_0.var())); th.log_axiom_instantiation(body); } mk_axiom(q_ge_0, mod_ge_0); mk_axiom(q_le_0, mod_ge_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(a.mk_lt(q, zero), a.mk_lt(a.mk_sub(mod, q), zero)); th.log_axiom_instantiation(body); } mk_axiom(q_le_0, ~mk_literal(a.mk_ge(a.mk_sub(mod, q), zero))); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(a.mk_lt(q, zero), a.mk_lt(a.mk_add(mod, q), zero)); th.log_axiom_instantiation(body); } mk_axiom(q_ge_0, ~mk_literal(a.mk_ge(a.mk_add(mod, q), zero))); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_ge_0.var())); th.log_axiom_instantiation(body); } mk_axiom(q_le_0, ~p_ge_0, div_ge_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_gt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_le_0.var())); th.log_axiom_instantiation(body); } mk_axiom(q_le_0, ~p_le_0, div_le_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_ge_0.var())), c.bool_var2expr(div_le_0.var())); th.log_axiom_instantiation(body); } mk_axiom(q_ge_0, ~p_ge_0, div_le_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(m.mk_and(a.mk_lt(q, zero), c.bool_var2expr(p_le_0.var())), c.bool_var2expr(div_ge_0.var())); th.log_axiom_instantiation(body); } mk_axiom(q_ge_0, ~p_le_0, div_ge_0); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } if (m_arith_params.m_arith_enum_const_mod && k.is_pos() && k < rational(8)) { unsigned _k = k.get_unsigned(); literal_buffer lits; expr_ref_vector exprs(m); for (unsigned j = 0; j < _k; ++j) { literal mod_j = th.mk_eq(mod, a.mk_int(j), false); lits.push_back(mod_j); exprs.push_back(c.bool_var2expr(mod_j.var())); ctx().mark_as_relevant(mod_j); } if (m.has_trace_stream()) { app_ref body(m); body = m.mk_or(exprs.size(), exprs.c_ptr()); th.log_axiom_instantiation(body); } ctx().mk_th_axiom(get_id(), lits.size(), lits.begin()); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; } } void mk_axiom(literal l) { ctx().mk_th_axiom(get_id(), false_literal, l); if (ctx().relevancy()) { ctx().mark_as_relevant(l); } } void mk_axiom(literal l1, literal l2) { if (l1 == false_literal) { mk_axiom(l2); return; } ctx().mk_th_axiom(get_id(), l1, l2); if (ctx().relevancy()) { ctx().mark_as_relevant(l1); ctx().add_rel_watch(~l1, ctx().bool_var2expr(l2.var())); } } void mk_axiom(literal l1, literal l2, literal l3) { ctx().mk_th_axiom(get_id(), l1, l2, l3); if (ctx().relevancy()) { ctx().mark_as_relevant(l1); ctx().add_rel_watch(~l1, ctx().bool_var2expr(l2.var())); ctx().add_rel_watch(~l2, ctx().bool_var2expr(l3.var())); } } literal mk_literal(expr* e) { expr_ref pinned(e, m); TRACE("mk_bool_var", tout << pinned << " " << pinned->get_id() << "\n";); if (!ctx().e_internalized(e)) { ctx().internalize(e, false); } return ctx().get_literal(e); } void init_search_eh() { m_arith_eq_adapter.init_search_eh(); m_num_conflicts = 0; } bool can_get_value(theory_var v) const { return (v != null_theory_var) && (v < static_cast(m_theory_var2var_index.size())) && (UINT_MAX != m_theory_var2var_index[v]) && (!m_variable_values.empty() || m_solver->is_term(m_theory_var2var_index[v])); } bool can_get_bound(theory_var v) const { return (v != null_theory_var) && (v < static_cast(m_theory_var2var_index.size())) && (UINT_MAX != m_theory_var2var_index[v]); } bool can_get_ivalue(theory_var v) const { if (v == null_theory_var || (v >= static_cast(m_theory_var2var_index.size()))) return false; return m_solver->var_is_registered(m_theory_var2var_index[v]); } mutable vector> m_todo_terms; lp::impq get_ivalue(theory_var v) const { SASSERT(can_get_ivalue(v)); lp::var_index vi = m_theory_var2var_index[v]; if (!m_solver->is_term(vi)) return m_solver->get_column_value(vi); m_todo_terms.push_back(std::make_pair(vi, rational::one())); lp::impq result(0); while (!m_todo_terms.empty()) { vi = m_todo_terms.back().first; rational coeff = m_todo_terms.back().second; m_todo_terms.pop_back(); if (m_solver->is_term(vi)) { const lp::lar_term& term = m_solver->get_term(vi); for (const auto & i: term) { m_todo_terms.push_back(std::make_pair(i.var(), coeff * i.coeff())); } } else { result += m_solver->get_column_value(vi) * coeff; } } return result; } rational get_value(theory_var v) const { if (v == null_theory_var || v >= static_cast(m_theory_var2var_index.size())) { TRACE("arith", tout << "Variable v" << v << " not internalized\n";); return rational::zero(); } lp::var_index vi = m_theory_var2var_index[v]; if (m_variable_values.count(vi) > 0) return m_variable_values[vi]; if (!m_solver->is_term(vi)) { TRACE("arith", tout << "not a term v" << v << "\n";); return rational::zero(); } m_todo_terms.push_back(std::make_pair(vi, rational::one())); rational result(0); while (!m_todo_terms.empty()) { lp::var_index wi = m_todo_terms.back().first; rational coeff = m_todo_terms.back().second; m_todo_terms.pop_back(); if (m_solver->is_term(wi)) { const lp::lar_term& term = m_solver->get_term(wi); for (const auto & i : term) { if (m_variable_values.count(i.var()) > 0) { result += m_variable_values[i.var()] * coeff * i.coeff(); } else { m_todo_terms.push_back(std::make_pair(i.var(), coeff * i.coeff())); } } } else { result += m_variable_values[wi] * coeff; } } m_variable_values[vi] = result; return result; } void init_variable_values() { reset_variable_values(); if (!m.canceled() && m_solver.get() && th.get_num_vars() > 0) { TRACE("arith", tout << "update variable values\n";); m_solver->get_model(m_variable_values); } } void reset_variable_values() { m_variable_values.clear(); } bool assume_eqs() { svector vars; theory_var sz = static_cast(th.get_num_vars()); for (theory_var v = 0; v < sz; ++v) { if (th.is_relevant_and_shared(get_enode(v))) { vars.push_back(m_theory_var2var_index[v]); } } if (vars.empty()) { return false; } init_variable_values(); TRACE("arith", for (theory_var v = 0; v < sz; ++v) { if (th.is_relevant_and_shared(get_enode(v))) { tout << "v" << v << " "; } } tout << "\n"; ); if (!m_use_nra_model) { m_solver->random_update(vars.size(), vars.c_ptr()); } m_model_eqs.reset(); TRACE("arith", display(tout);); unsigned old_sz = m_assume_eq_candidates.size(); bool result = false; int start = ctx().get_random_value(); for (theory_var i = 0; i < sz; ++i) { theory_var v = (i + start) % sz; enode* n1 = get_enode(v); if (!th.is_relevant_and_shared(n1)) { continue; } if (!can_get_value(v)) { continue; } theory_var other = m_model_eqs.insert_if_not_there(v); TRACE("arith", tout << "insert: v" << v << " := " << get_value(v) << " found: v" << other << "\n";); if (other == v) { continue; } enode* n2 = get_enode(other); if (n1->get_root() != n2->get_root()) { TRACE("arith", tout << enode_eq_pp(enode_pair(n1, n2), ctx()); tout << "v" << v << " = " << "v" << other << "\n";); m_assume_eq_candidates.push_back(std::make_pair(v, other)); result = true; } } if (result) { ctx().push_trail(restore_size_trail, false>(m_assume_eq_candidates, old_sz)); } return delayed_assume_eqs(); } bool delayed_assume_eqs() { if (m_assume_eq_head == m_assume_eq_candidates.size()) return false; ctx().push_trail(value_trail(m_assume_eq_head)); while (m_assume_eq_head < m_assume_eq_candidates.size()) { std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; theory_var v1 = p.first; theory_var v2 = p.second; enode* n1 = get_enode(v1); enode* n2 = get_enode(v2); m_assume_eq_head++; CTRACE("arith", is_eq(v1, v2) && n1->get_root() != n2->get_root(), tout << "assuming eq: v" << v1 << " = v" << v2 << "\n";); if (is_eq(v1, v2) && n1->get_root() != n2->get_root() && th.assume_eq(n1, n2)) { return true; } } return false; } bool is_eq(theory_var v1, theory_var v2) { if (m_use_nra_model) { return m_nra->am().eq(nl_value(v1, *m_a1), nl_value(v2, *m_a2)); } else { return get_value(v1) == get_value(v2); } } bool has_delayed_constraints() const { return !m_asserted_atoms.empty(); } final_check_status final_check_eh() { IF_VERBOSE(12, verbose_stream() << "final-check " << m_solver->get_status() << "\n"); m_use_nra_model = false; lbool is_sat = l_true; if (m_solver->get_status() != lp::lp_status::OPTIMAL) { is_sat = make_feasible(); } final_check_status st = FC_DONE; switch (is_sat) { case l_true: if (delayed_assume_eqs()) { return FC_CONTINUE; } if (assume_eqs()) { return FC_CONTINUE; } switch (check_lia()) { case l_true: break; case l_false: return FC_CONTINUE; case l_undef: TRACE("arith", tout << "check-lia giveup\n";); st = FC_CONTINUE; break; } switch (check_nra()) { case l_true: break; case l_false: return FC_CONTINUE; case l_undef: TRACE("arith", tout << "check-nra giveup\n";); st = FC_GIVEUP; break; } if (m_not_handled != nullptr) { TRACE("arith", tout << "unhandled operator " << mk_pp(m_not_handled, m) << "\n";); st = FC_GIVEUP; } return st; case l_false: set_conflict(); return FC_CONTINUE; case l_undef: TRACE("arith", tout << "check feasiable is undef\n";); return m.canceled() ? FC_CONTINUE : FC_GIVEUP; default: UNREACHABLE(); break; } TRACE("arith", tout << "default giveup\n";); return FC_GIVEUP; } // create a bound atom representing term >= k is lower_bound is true, and term <= k if it is false app_ref mk_bound(lp::lar_term const& term, rational const& k, bool lower_bound) { bool is_int = k.is_int(); rational offset = -k; u_map coeffs; term2coeffs(term, coeffs, rational::one(), offset); offset.neg(); TRACE("arith", m_solver->print_term(term, tout << "term: ") << "\n"; for (auto const& kv : coeffs) { tout << "v" << kv.m_key << " * " << kv.m_value << "\n"; } tout << offset << "\n"; rational g(0); for (auto const& kv : coeffs) { g = gcd(g, kv.m_value); } tout << "gcd: " << g << "\n"; ); if (is_int) { // 3x + 6y >= 5 -> x + 3y >= 5/3, then x + 3y >= 2 // 3x + 6y <= 5 -> x + 3y <= 1 rational g = gcd_reduce(coeffs); if (!g.is_one()) { if (lower_bound) { TRACE("arith", tout << "lower: " << offset << " / " << g << " = " << offset / g << " >= " << ceil(offset / g) << "\n";); offset = ceil(offset / g); } else { TRACE("arith", tout << "upper: " << offset << " / " << g << " = " << offset / g << " <= " << floor(offset / g) << "\n";); offset = floor(offset / g); } } } if (!coeffs.empty() && coeffs.begin()->m_value.is_neg()) { offset.neg(); lower_bound = !lower_bound; for (auto& kv : coeffs) kv.m_value.neg(); } app_ref atom(m); app_ref t = coeffs2app(coeffs, rational::zero(), is_int); if (lower_bound) { atom = a.mk_ge(t, a.mk_numeral(offset, is_int)); } else { atom = a.mk_le(t, a.mk_numeral(offset, is_int)); } TRACE("arith", tout << t << ": " << atom << "\n"; m_solver->print_term(term, tout << "bound atom: ") << (lower_bound?" >= ":" <= ") << k << "\n";); ctx().internalize(atom, true); ctx().mark_as_relevant(atom.get()); return atom; } bool make_sure_all_vars_have_bounds() { if (!m_has_int) { return true; } unsigned nv = std::min(th.get_num_vars(), m_theory_var2var_index.size()); bool all_bounded = true; for (unsigned v = 0; v < nv; ++v) { lp::var_index vi = m_theory_var2var_index[v]; if (vi == UINT_MAX) continue; if (!m_solver->is_term(vi) && !var_has_bound(vi, true) && !var_has_bound(vi, false)) { lp::lar_term term; term.add_monomial(rational::one(), vi); app_ref b = mk_bound(term, rational::zero(), true); TRACE("arith", tout << "added bound " << b << "\n";); IF_VERBOSE(2, verbose_stream() << "bound: " << b << "\n"); all_bounded = false; } } return all_bounded; } /** * n = (div p q) * * (div p q) * q + (mod p q) = p, (mod p q) >= 0 * * 0 < q => (p/q <= v(p)/v(q) => n <= floor(v(p)/v(q))) * 0 < q => (v(p)/v(q) <= p/q => v(p)/v(q) - 1 < n) * */ bool is_bounded(expr* n) { expr* x = nullptr, *y = nullptr; while (true) { if (a.is_idiv(n, x, y) && a.is_numeral(y)) { n = x; } else if (a.is_mod(n, x, y) && a.is_numeral(y)) { return true; } else if (a.is_numeral(n)) { return true; } else { return false; } } } bool check_idiv_bounds() { if (m_idiv_terms.empty()) { return true; } init_variable_values(); bool all_divs_valid = true; for (expr* n : m_idiv_terms) { expr* p = nullptr, *q = nullptr; VERIFY(a.is_idiv(n, p, q)); theory_var v = mk_var(n); theory_var v1 = mk_var(p); rational r1 = get_value(v1); rational r2; if (!r1.is_int() || r1.is_neg()) { // TBD // r1 = 223/4, r2 = 2, r = 219/8 // take ceil(r1), floor(r1), ceil(r2), floor(r2), for floor(r2) > 0 // then // p/q <= ceil(r1)/floor(r2) => n <= div(ceil(r1), floor(r2)) // p/q >= floor(r1)/ceil(r2) => n >= div(floor(r1), ceil(r2)) continue; } if (a.is_numeral(q, r2) && r2.is_pos()) { rational val_v = get_value(v); if (val_v == div(r1, r2)) continue; if (!is_bounded(n)) { TRACE("arith", tout << "unbounded " << expr_ref(n, m) << "\n";); continue; } TRACE("arith", tout << get_value(v) << " != " << r1 << " div " << r2 << "\n";); rational div_r = div(r1, r2); // p <= q * div(r1, q) + q - 1 => div(p, q) <= div(r1, r2) // p >= q * div(r1, q) => div(r1, q) <= div(p, q) rational mul(1); rational hi = r2 * div_r + r2 - 1; rational lo = r2 * div_r; // used to normalize inequalities so they // don't appear as 8*x >= 15, but x >= 2 expr *n1 = nullptr, *n2 = nullptr; if (a.is_mul(p, n1, n2) && is_numeral(n1, mul) && mul.is_pos()) { p = n2; hi = floor(hi/mul); lo = ceil(lo/mul); } literal p_le_r1 = mk_literal(a.mk_le(p, a.mk_numeral(hi, true))); literal p_ge_r1 = mk_literal(a.mk_ge(p, a.mk_numeral(lo, true))); literal n_le_div = mk_literal(a.mk_le(n, a.mk_numeral(div_r, true))); literal n_ge_div = mk_literal(a.mk_ge(n, a.mk_numeral(div_r, true))); if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(ctx().bool_var2expr(p_le_r1.var()), ctx().bool_var2expr(n_le_div.var())); th.log_axiom_instantiation(body); } mk_axiom(~p_le_r1, n_le_div); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; if (m.has_trace_stream()) { app_ref body(m); body = m.mk_implies(ctx().bool_var2expr(p_ge_r1.var()), ctx().bool_var2expr(n_ge_div.var())); th.log_axiom_instantiation(body); } mk_axiom(~p_ge_r1, n_ge_div); if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; all_divs_valid = false; TRACE("arith", tout << r1 << " div " << r2 << "\n"; literal_vector lits; lits.push_back(~p_le_r1); lits.push_back(n_le_div); ctx().display_literals_verbose(tout, lits) << "\n\n"; lits[0] = ~p_ge_r1; lits[1] = n_ge_div; ctx().display_literals_verbose(tout, lits) << "\n";); continue; } } return all_divs_valid; } expr_ref var2expr(lp::var_index v) { std::ostringstream name; name << "v" << m_solver->local2external(v); return expr_ref(m.mk_const(symbol(name.str().c_str()), a.mk_int()), m); } expr_ref multerm(rational const& r, expr* e) { if (r.is_one()) return expr_ref(e, m); return expr_ref(a.mk_mul(a.mk_numeral(r, true), e), m); } expr_ref term2expr(lp::lar_term const& term) { expr_ref t(m); expr_ref_vector ts(m); for (auto const& p : term) { lp::var_index wi = p.var(); if (m_solver->is_term(wi)) { ts.push_back(multerm(p.coeff(), term2expr(m_solver->get_term(wi)))); } else { ts.push_back(multerm(p.coeff(), var2expr(wi))); } } if (ts.size() == 1) { t = ts.back(); } else { t = a.mk_add(ts.size(), ts.c_ptr()); } return t; } expr_ref constraint2fml(lp::constraint_index ci) { lp::lar_base_constraint const& c = *m_solver->constraints()[ci]; expr_ref fml(m); expr_ref_vector ts(m); rational rhs = c.m_right_side; for (auto cv : c.get_left_side_coefficients()) { ts.push_back(multerm(cv.first, var2expr(cv.second))); } switch (c.m_kind) { case lp::LE: fml = a.mk_le(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; case lp::LT: fml = a.mk_lt(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; case lp::GE: fml = a.mk_ge(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; case lp::GT: fml = a.mk_gt(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; case lp::EQ: fml = m.mk_eq(a.mk_add(ts.size(), ts.c_ptr()), a.mk_numeral(rhs, true)); break; } return fml; } void dump_cut_lemma(std::ostream& out, lp::lar_term const& term, lp::mpq const& k, lp::explanation const& ex, bool upper) { m_solver->print_term(term, out << "bound: "); out << (upper?" <= ":" >= ") << k << "\n"; for (auto const& p : term) { lp::var_index wi = p.var(); out << p.coeff() << " * "; if (m_solver->is_term(wi)) { m_solver->print_term(m_solver->get_term(wi), out) << "\n"; } else { out << "v" << m_solver->local2external(wi) << "\n"; } } for (auto const& ev : ex.m_explanation) { m_solver->print_constraint(ev.second, out << ev.first << ": "); } expr_ref_vector fmls(m); for (auto const& ev : ex.m_explanation) { fmls.push_back(constraint2fml(ev.second)); } expr_ref t(term2expr(term), m); if (upper) { fmls.push_back(m.mk_not(a.mk_ge(t, a.mk_numeral(k, true)))); } else { fmls.push_back(m.mk_not(a.mk_le(t, a.mk_numeral(k, true)))); } ast_pp_util visitor(m); visitor.collect(fmls); visitor.display_decls(out); visitor.display_asserts(out, fmls, true); out << "(check-sat)\n"; } lbool check_lia() { if (m.canceled()) { TRACE("arith", tout << "canceled\n";); return l_undef; } lbool lia_check = l_undef; if (!check_idiv_bounds()) { return l_false; } m_explanation.reset(); switch (m_lia->check()) { case lp::lia_move::sat: lia_check = l_true; break; case lp::lia_move::branch: { TRACE("arith", tout << "branch\n";); app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper()); if (m.has_trace_stream()) { app_ref body(m); body = m.mk_or(b, m.mk_not(b)); th.log_axiom_instantiation(body); m.trace_stream() << "[end-of-instance]\n"; } IF_VERBOSE(2, verbose_stream() << "branch " << b << "\n";); // branch on term >= k + 1 // branch on term <= k // TBD: ctx().force_phase(ctx().get_literal(b)); // at this point we have a new unassigned atom that the // SAT core assigns a value to lia_check = l_false; break; } case lp::lia_move::cut: { TRACE("arith", tout << "cut\n";); ++m_stats.m_gomory_cuts; // m_explanation implies term <= k app_ref b = mk_bound(m_lia->get_term(), m_lia->get_offset(), !m_lia->is_upper()); if (m.has_trace_stream()) { th.log_axiom_instantiation(b); m.trace_stream() << "[end-of-instance]\n"; } IF_VERBOSE(2, verbose_stream() << "cut " << b << "\n"); TRACE("arith", dump_cut_lemma(tout, m_lia->get_term(), m_lia->get_offset(), m_lia->get_explanation(), m_lia->is_upper());); m_eqs.reset(); m_core.reset(); m_params.reset(); for (auto const& ev : m_lia->get_explanation().m_explanation) { if (!ev.first.is_zero()) { set_evidence(ev.second); } } literal lit(ctx().get_bool_var(b), false); TRACE("arith", ctx().display_lemma_as_smt_problem(tout << "new cut:\n", m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); display(tout);); assign(lit); lia_check = l_false; break; } case lp::lia_move::conflict: TRACE("arith", tout << "conflict\n";); // ex contains unsat core m_explanation = m_lia->get_explanation().m_explanation; set_conflict1(); lia_check = l_false; break; case lp::lia_move::undef: TRACE("arith", tout << "lia undef\n";); lia_check = l_undef; break; case lp::lia_move::continue_with_check: lia_check = l_undef; break; default: UNREACHABLE(); } return lia_check; } lbool check_nra() { m_use_nra_model = false; if (m.canceled()) { TRACE("arith", tout << "canceled\n";); return l_undef; } if (!m_nra) return l_true; if (!m_nra->need_check()) return l_true; m_a1 = nullptr; m_a2 = nullptr; lbool r = m_nra->check(m_explanation); m_a1 = alloc(scoped_anum, m_nra->am()); m_a2 = alloc(scoped_anum, m_nra->am()); switch (r) { case l_false: set_conflict1(); break; case l_true: m_use_nra_model = true; if (assume_eqs()) { return l_false; } break; case l_undef: TRACE("arith", tout << "nra-undef\n";); default: break; } return r; } /** \brief We must redefine this method, because theory of arithmetic contains underspecified operators such as division by 0. (/ a b) is essentially an uninterpreted function when b = 0. Thus, 'a' must be considered a shared var if it is the child of an underspecified operator. if merge(a / b, x + y) and a / b is root, then x + y become shared and all z + u in equivalence class of x + y. TBD: when the set of underspecified subterms is small, compute the shared variables below it. Recompute the set if there are merges that invalidate it. Use the set to determine if a variable is shared. */ bool is_shared(theory_var v) const { if (m_underspecified.empty()) { return false; } enode * n = get_enode(v); enode * r = n->get_root(); unsigned usz = m_underspecified.size(); TRACE("shared", tout << ctx().get_scope_level() << " " << v << " " << r->get_num_parents() << "\n";); if (r->get_num_parents() > 2*usz) { for (unsigned i = 0; i < usz; ++i) { app* u = m_underspecified[i]; unsigned sz = u->get_num_args(); for (unsigned j = 0; j < sz; ++j) { if (ctx().get_enode(u->get_arg(j))->get_root() == r) { return true; } } } } else { for (enode * parent : r->get_const_parents()) { if (is_underspecified(parent->get_owner())) { return true; } } } return false; } bool can_propagate() { return m_asserted_atoms.size() > m_asserted_qhead; } void propagate() { flush_bound_axioms(); if (!can_propagate()) { return; } while (m_asserted_qhead < m_asserted_atoms.size() && !ctx().inconsistent()) { bool_var bv = m_asserted_atoms[m_asserted_qhead].m_bv; bool is_true = m_asserted_atoms[m_asserted_qhead].m_is_true; m_to_check.push_back(bv); lp_api::bound& b = *m_bool_var2bound.find(bv); assert_bound(bv, is_true, b); ++m_asserted_qhead; } if (ctx().inconsistent()) { m_to_check.reset(); return; } lbool lbl = make_feasible(); switch(lbl) { case l_false: TRACE("arith", tout << "propagation conflict\n";); set_conflict(); break; case l_true: propagate_basic_bounds(); propagate_bounds_with_lp_solver(); break; case l_undef: break; } } void propagate_bounds_with_lp_solver() { if (BP_NONE == propagation_mode()) { return; } int num_of_p = m_solver->settings().st().m_num_of_implied_bounds; (void)num_of_p; local_bound_propagator bp(*this); m_solver->propagate_bounds_for_touched_rows(bp); if (m.canceled()) { return; } int new_num_of_p = m_solver->settings().st().m_num_of_implied_bounds; (void)new_num_of_p; CTRACE("arith", new_num_of_p > num_of_p, tout << "found " << new_num_of_p << " implied bounds\n";); if (m_solver->get_status() == lp::lp_status::INFEASIBLE) { set_conflict(); } else { for (unsigned i = 0; !m.canceled() && !ctx().inconsistent() && i < bp.m_ibounds.size(); ++i) { propagate_lp_solver_bound(bp.m_ibounds[i]); } } } bool bound_is_interesting(unsigned vi, lp::lconstraint_kind kind, const rational & bval) const { theory_var v; if (m_solver->is_term(vi)) { v = m_term_index2theory_var.get(m_solver->adjust_term_index(vi), null_theory_var); } else { v = m_var_index2theory_var.get(vi, null_theory_var); } if (v == null_theory_var) return false; if (m_unassigned_bounds[v] == 0 || m_bounds.size() <= static_cast(v)) { return false; } lp_bounds const& bounds = m_bounds[v]; for (unsigned i = 0; i < bounds.size(); ++i) { lp_api::bound* b = bounds[i]; if (ctx().get_assignment(b->get_bv()) != l_undef) { continue; } literal lit = is_bound_implied(kind, bval, *b); if (lit == null_literal) { continue; } return true; } return false; } struct local_bound_propagator: public lp::bound_propagator { imp & m_imp; local_bound_propagator(imp& i) : bound_propagator(*i.m_solver), m_imp(i) {} bool bound_is_interesting(unsigned j, lp::lconstraint_kind kind, const rational & v) override { return m_imp.bound_is_interesting(j, kind, v); } void consume(rational const& v, lp::constraint_index j) override { m_imp.set_evidence(j); m_imp.m_explanation.push_back(std::make_pair(v, j)); } }; void propagate_lp_solver_bound(lp::implied_bound& be) { theory_var v; lp::var_index vi = be.m_j; if (m_solver->is_term(vi)) { v = m_term_index2theory_var.get(m_solver->adjust_term_index(vi), null_theory_var); } else { v = m_var_index2theory_var.get(vi, null_theory_var); } if (v == null_theory_var) return; TRACE("arith", tout << "v" << v << " " << be.kind() << " " << be.m_bound << "\n"; // if (m_unassigned_bounds[v] == 0) m_solver->print_bound_evidence(be, tout); ); if (m_unassigned_bounds[v] == 0 || m_bounds.size() <= static_cast(v)) { TRACE("arith", tout << "return\n";); return; } lp_bounds const& bounds = m_bounds[v]; bool first = true; for (unsigned i = 0; i < bounds.size(); ++i) { lp_api::bound* b = bounds[i]; if (ctx().get_assignment(b->get_bv()) != l_undef) { continue; } literal lit = is_bound_implied(be.kind(), be.m_bound, *b); if (lit == null_literal) { continue; } TRACE("arith", tout << lit << " bound: " << *b << " first: " << first << "\n";); m_solver->settings().st().m_num_of_implied_bounds ++; if (first) { first = false; m_core.reset(); m_eqs.reset(); m_params.reset(); m_explanation.clear(); local_bound_propagator bp(*this); m_solver->explain_implied_bound(be, bp); } CTRACE("arith", m_unassigned_bounds[v] == 0, tout << "missed bound\n";); updt_unassigned_bounds(v, -1); TRACE("arith", ctx().display_literals_verbose(tout, m_core); tout << "\n --> "; ctx().display_literal_verbose(tout, lit); tout << "\n"; display_evidence(tout, m_explanation); m_solver->print_implied_bound(be, tout); ); DEBUG_CODE( for (auto& lit : m_core) { SASSERT(ctx().get_assignment(lit) == l_true); }); ++m_stats.m_bound_propagations1; assign(lit); } } literal_vector m_core2; void assign(literal lit) { // SASSERT(validate_assign(lit)); dump_assign(lit); if (m_core.size() < small_lemma_size() && m_eqs.empty()) { m_core2.reset(); for (auto const& c : m_core) { m_core2.push_back(~c); } m_core2.push_back(lit); justification * js = nullptr; if (proofs_enabled()) { js = alloc(theory_lemma_justification, get_id(), ctx(), m_core2.size(), m_core2.c_ptr(), m_params.size(), m_params.c_ptr()); } ctx().mk_clause(m_core2.size(), m_core2.c_ptr(), js, CLS_TH_LEMMA, nullptr); } else { ctx().assign( lit, ctx().mk_justification( ext_theory_propagation_justification( get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit, m_params.size(), m_params.c_ptr()))); } } literal is_bound_implied(lp::lconstraint_kind k, rational const& value, lp_api::bound const& b) const { if ((k == lp::LE || k == lp::LT) && b.get_bound_kind() == lp_api::upper_t && value <= b.get_value()) { TRACE("arith", tout << "v <= value <= b.get_value() => v <= b.get_value() \n";); return literal(b.get_bv(), false); } if ((k == lp::GE || k == lp::GT) && b.get_bound_kind() == lp_api::lower_t && b.get_value() <= value) { TRACE("arith", tout << "b.get_value() <= value <= v => b.get_value() <= v \n";); return literal(b.get_bv(), false); } if (k == lp::LE && b.get_bound_kind() == lp_api::lower_t && value < b.get_value()) { TRACE("arith", tout << "v <= value < b.get_value() => v < b.get_value()\n";); return literal(b.get_bv(), true); } if (k == lp::LT && b.get_bound_kind() == lp_api::lower_t && value <= b.get_value()) { TRACE("arith", tout << "v < value <= b.get_value() => v < b.get_value()\n";); return literal(b.get_bv(), true); } if (k == lp::GE && b.get_bound_kind() == lp_api::upper_t && b.get_value() < value) { TRACE("arith", tout << "b.get_value() < value <= v => b.get_value() < v\n";); return literal(b.get_bv(), true); } if (k == lp::GT && b.get_bound_kind() == lp_api::upper_t && b.get_value() <= value) { TRACE("arith", tout << "b.get_value() <= value < v => b.get_value() < v\n";); return literal(b.get_bv(), true); } return null_literal; } void mk_bound_axioms(lp_api::bound& b) { if (!ctx().is_searching()) { // // NB. We make an assumption that user push calls propagation // before internal scopes are pushed. This flushes all newly // asserted atoms into the right context. // m_new_bounds.push_back(&b); return; } theory_var v = b.get_var(); lp_api::bound_kind kind1 = b.get_bound_kind(); rational const& k1 = b.get_value(); lp_bounds & bounds = m_bounds[v]; lp_api::bound* end = nullptr; lp_api::bound* lo_inf = end, *lo_sup = end; lp_api::bound* hi_inf = end, *hi_sup = end; for (lp_api::bound* other : bounds) { if (other == &b) continue; if (b.get_bv() == other->get_bv()) continue; lp_api::bound_kind kind2 = other->get_bound_kind(); rational const& k2 = other->get_value(); if (k1 == k2 && kind1 == kind2) { // the bounds are equivalent. continue; } SASSERT(k1 != k2 || kind1 != kind2); if (kind2 == lp_api::lower_t) { if (k2 < k1) { if (lo_inf == end || k2 > lo_inf->get_value()) { lo_inf = other; } } else if (lo_sup == end || k2 < lo_sup->get_value()) { lo_sup = other; } } else if (k2 < k1) { if (hi_inf == end || k2 > hi_inf->get_value()) { hi_inf = other; } } else if (hi_sup == end || k2 < hi_sup->get_value()) { hi_sup = other; } } if (lo_inf != end) mk_bound_axiom(b, *lo_inf); if (lo_sup != end) mk_bound_axiom(b, *lo_sup); if (hi_inf != end) mk_bound_axiom(b, *hi_inf); if (hi_sup != end) mk_bound_axiom(b, *hi_sup); } void mk_bound_axiom(lp_api::bound& b1, lp_api::bound& b2) { theory_var v = b1.get_var(); literal l1(b1.get_bv()); literal l2(b2.get_bv()); rational const& k1 = b1.get_value(); rational const& k2 = b2.get_value(); lp_api::bound_kind kind1 = b1.get_bound_kind(); lp_api::bound_kind kind2 = b2.get_bound_kind(); bool v_is_int = is_int(v); SASSERT(v == b2.get_var()); if (k1 == k2 && kind1 == kind2) return; SASSERT(k1 != k2 || kind1 != kind2); parameter coeffs[3] = { parameter(symbol("farkas")), parameter(rational(1)), parameter(rational(1)) }; if (kind1 == lp_api::lower_t) { if (kind2 == lp_api::lower_t) { if (k2 <= k1) { mk_clause(~l1, l2, 3, coeffs); } else { mk_clause(l1, ~l2, 3, coeffs); } } else if (k1 <= k2) { // k1 <= k2, k1 <= x or x <= k2 mk_clause(l1, l2, 3, coeffs); } else { // k1 > hi_inf, k1 <= x => ~(x <= hi_inf) mk_clause(~l1, ~l2, 3, coeffs); if (v_is_int && k1 == k2 + rational(1)) { // k1 <= x or x <= k1-1 mk_clause(l1, l2, 3, coeffs); } } } else if (kind2 == lp_api::lower_t) { if (k1 >= k2) { // k1 >= lo_inf, k1 >= x or lo_inf <= x mk_clause(l1, l2, 3, coeffs); } else { // k1 < k2, k2 <= x => ~(x <= k1) mk_clause(~l1, ~l2, 3, coeffs); if (v_is_int && k1 == k2 - rational(1)) { // x <= k1 or k1+l <= x mk_clause(l1, l2, 3, coeffs); } } } else { // kind1 == A_UPPER, kind2 == A_UPPER if (k1 >= k2) { // k1 >= k2, x <= k2 => x <= k1 mk_clause(l1, ~l2, 3, coeffs); } else { // k1 <= hi_sup , x <= k1 => x <= hi_sup mk_clause(~l1, l2, 3, coeffs); } } } typedef lp_bounds::iterator iterator; void flush_bound_axioms() { CTRACE("arith", !m_new_bounds.empty(), tout << "flush bound axioms\n";); while (!m_new_bounds.empty()) { lp_bounds atoms; atoms.push_back(m_new_bounds.back()); m_new_bounds.pop_back(); theory_var v = atoms.back()->get_var(); for (unsigned i = 0; i < m_new_bounds.size(); ++i) { if (m_new_bounds[i]->get_var() == v) { atoms.push_back(m_new_bounds[i]); m_new_bounds[i] = m_new_bounds.back(); m_new_bounds.pop_back(); --i; } } CTRACE("arith_verbose", !atoms.empty(), for (unsigned i = 0; i < atoms.size(); ++i) { atoms[i]->display(tout); tout << "\n"; }); lp_bounds occs(m_bounds[v]); std::sort(atoms.begin(), atoms.end(), compare_bounds()); std::sort(occs.begin(), occs.end(), compare_bounds()); iterator begin1 = occs.begin(); iterator begin2 = occs.begin(); iterator end = occs.end(); begin1 = first(lp_api::lower_t, begin1, end); begin2 = first(lp_api::upper_t, begin2, end); iterator lo_inf = begin1, lo_sup = begin1; iterator hi_inf = begin2, hi_sup = begin2; bool flo_inf, fhi_inf, flo_sup, fhi_sup; ptr_addr_hashtable visited; for (unsigned i = 0; i < atoms.size(); ++i) { lp_api::bound* a1 = atoms[i]; iterator lo_inf1 = next_inf(a1, lp_api::lower_t, lo_inf, end, flo_inf); iterator hi_inf1 = next_inf(a1, lp_api::upper_t, hi_inf, end, fhi_inf); iterator lo_sup1 = next_sup(a1, lp_api::lower_t, lo_sup, end, flo_sup); iterator hi_sup1 = next_sup(a1, lp_api::upper_t, hi_sup, end, fhi_sup); if (lo_inf1 != end) lo_inf = lo_inf1; if (lo_sup1 != end) lo_sup = lo_sup1; if (hi_inf1 != end) hi_inf = hi_inf1; if (hi_sup1 != end) hi_sup = hi_sup1; if (!flo_inf) lo_inf = end; if (!fhi_inf) hi_inf = end; if (!flo_sup) lo_sup = end; if (!fhi_sup) hi_sup = end; visited.insert(a1); if (lo_inf1 != end && lo_inf != end && !visited.contains(*lo_inf)) mk_bound_axiom(*a1, **lo_inf); if (lo_sup1 != end && lo_sup != end && !visited.contains(*lo_sup)) mk_bound_axiom(*a1, **lo_sup); if (hi_inf1 != end && hi_inf != end && !visited.contains(*hi_inf)) mk_bound_axiom(*a1, **hi_inf); if (hi_sup1 != end && hi_sup != end && !visited.contains(*hi_sup)) mk_bound_axiom(*a1, **hi_sup); } } } struct compare_bounds { bool operator()(lp_api::bound* a1, lp_api::bound* a2) const { return a1->get_value() < a2->get_value(); } }; lp_bounds::iterator first( lp_api::bound_kind kind, iterator it, iterator end) { for (; it != end; ++it) { lp_api::bound* a = *it; if (a->get_bound_kind() == kind) return it; } return end; } lp_bounds::iterator next_inf( lp_api::bound* a1, lp_api::bound_kind kind, iterator it, iterator end, bool& found_compatible) { rational const & k1(a1->get_value()); iterator result = end; found_compatible = false; for (; it != end; ++it) { lp_api::bound * a2 = *it; if (a1 == a2) continue; if (a2->get_bound_kind() != kind) continue; rational const & k2(a2->get_value()); found_compatible = true; if (k2 <= k1) { result = it; } else { break; } } return result; } lp_bounds::iterator next_sup( lp_api::bound* a1, lp_api::bound_kind kind, iterator it, iterator end, bool& found_compatible) { rational const & k1(a1->get_value()); found_compatible = false; for (; it != end; ++it) { lp_api::bound * a2 = *it; if (a1 == a2) continue; if (a2->get_bound_kind() != kind) continue; rational const & k2(a2->get_value()); found_compatible = true; if (k1 < k2) { return it; } } return end; } void propagate_basic_bounds() { for (auto const& bv : m_to_check) { lp_api::bound& b = *m_bool_var2bound.find(bv); propagate_bound(bv, ctx().get_assignment(bv) == l_true, b); if (ctx().inconsistent()) break; } m_to_check.reset(); } // for glb lo': lo' < lo: // lo <= x -> lo' <= x // lo <= x -> ~(x <= lo') // for lub hi': hi' > hi // x <= hi -> x <= hi' // x <= hi -> ~(x >= hi') void propagate_bound(bool_var bv, bool is_true, lp_api::bound& b) { if (BP_NONE == propagation_mode()) { return; } lp_api::bound_kind k = b.get_bound_kind(); theory_var v = b.get_var(); inf_rational val = b.get_value(is_true); lp_bounds const& bounds = m_bounds[v]; SASSERT(!bounds.empty()); if (bounds.size() == 1) return; if (m_unassigned_bounds[v] == 0) return; bool v_is_int = is_int(v); literal lit1(bv, !is_true); literal lit2 = null_literal; bool find_glb = (is_true == (k == lp_api::lower_t)); TRACE("arith", tout << "find_glb: " << find_glb << " is_true: " << is_true << " k: " << k << " is_lower: " << (k == lp_api::lower_t) << "\n";); if (find_glb) { rational glb; lp_api::bound* lb = nullptr; for (lp_api::bound* b2 : bounds) { if (b2 == &b) continue; rational const& val2 = b2->get_value(); if (((is_true || v_is_int) ? val2 < val : val2 <= val) && (!lb || glb < val2)) { lb = b2; glb = val2; } } if (!lb) return; bool sign = lb->get_bound_kind() != lp_api::lower_t; lit2 = literal(lb->get_bv(), sign); } else { rational lub; lp_api::bound* ub = nullptr; for (lp_api::bound* b2 : bounds) { if (b2 == &b) continue; rational const& val2 = b2->get_value(); if (((is_true || v_is_int) ? val < val2 : val <= val2) && (!ub || val2 < lub)) { ub = b2; lub = val2; } } if (!ub) return; bool sign = ub->get_bound_kind() != lp_api::upper_t; lit2 = literal(ub->get_bv(), sign); } TRACE("arith", ctx().display_literal_verbose(tout, lit1); ctx().display_literal_verbose(tout << " => ", lit2); tout << "\n";); updt_unassigned_bounds(v, -1); ++m_stats.m_bound_propagations2; m_params.reset(); m_core.reset(); m_eqs.reset(); m_core.push_back(lit1); m_params.push_back(parameter(symbol("farkas"))); m_params.push_back(parameter(rational(1))); m_params.push_back(parameter(rational(1))); assign(lit2); ++m_stats.m_bounds_propagations; } svector m_todo_vars; void add_use_lists(lp_api::bound* b) { theory_var v = b->get_var(); lp::var_index vi = get_var_index(v); if (!m_solver->is_term(vi)) { return; } m_todo_vars.push_back(vi); while (!m_todo_vars.empty()) { vi = m_todo_vars.back(); m_todo_vars.pop_back(); lp::lar_term const& term = m_solver->get_term(vi); for (auto const& p : term) { lp::var_index wi = p.var(); if (m_solver->is_term(wi)) { m_todo_vars.push_back(wi); } else { unsigned w = m_var_index2theory_var[wi]; m_use_list.reserve(w + 1, ptr_vector()); m_use_list[w].push_back(b); } } } } void del_use_lists(lp_api::bound* b) { theory_var v = b->get_var(); lp::var_index vi = m_theory_var2var_index[v]; if (!m_solver->is_term(vi)) { return; } m_todo_vars.push_back(vi); while (!m_todo_vars.empty()) { vi = m_todo_vars.back(); m_todo_vars.pop_back(); lp::lar_term const& term = m_solver->get_term(vi); for (auto const& coeff : term) { lp::var_index wi = coeff.var(); if (m_solver->is_term(wi)) { m_todo_vars.push_back(wi); } else { unsigned w = m_var_index2theory_var[wi]; SASSERT(m_use_list[w].back() == b); m_use_list[w].pop_back(); } } } } // // propagate bounds to compound terms // The idea is that if bounds on all variables in an inequality ax + by + cz >= k // have been assigned we may know the truth value of the inequality by using simple // bounds propagation. // void propagate_bound_compound(bool_var bv, bool is_true, lp_api::bound& b) { theory_var v = b.get_var(); TRACE("arith", tout << mk_pp(get_owner(v), m) << "\n";); if (static_cast(v) >= m_use_list.size()) { return; } for (auto const& vb : m_use_list[v]) { if (ctx().get_assignment(vb->get_bv()) != l_undef) { TRACE("arith_verbose", display_bound(tout << "assigned ", *vb) << "\n";); continue; } inf_rational r; // x + y // x >= 0, y >= 1 -> x + y >= 1 // x <= 0, y <= 2 -> x + y <= 2 literal lit = null_literal; if (lp_api::lower_t == vb->get_bound_kind()) { if (get_glb(*vb, r) && r >= vb->get_value()) { // vb is assigned true lit = literal(vb->get_bv(), false); } else if (get_lub(*vb, r) && r < vb->get_value()) { // vb is assigned false lit = literal(vb->get_bv(), true); } } else { if (get_glb(*vb, r) && r > vb->get_value()) { // VB <= value < val(VB) lit = literal(vb->get_bv(), true); } else if (get_lub(*vb, r) && r <= vb->get_value()) { // val(VB) <= value lit = literal(vb->get_bv(), false); } } if (lit != null_literal) { TRACE("arith", ctx().display_literals_verbose(tout, m_core); tout << "\n --> "; ctx().display_literal_verbose(tout, lit); tout << "\n"; ); assign(lit); } else { TRACE("arith_verbose", display_bound(tout << "skip ", *vb) << "\n";); } } } bool get_lub(lp_api::bound const& b, inf_rational& lub) { return get_bound(b, lub, true); } bool get_glb(lp_api::bound const& b, inf_rational& glb) { return get_bound(b, glb, false); } std::ostream& display_bound(std::ostream& out, lp_api::bound const& b) { return out << mk_pp(ctx().bool_var2expr(b.get_bv()), m); } bool get_bound(lp_api::bound const& b, inf_rational& r, bool is_lub) { m_core.reset(); m_eqs.reset(); m_params.reset(); r.reset(); theory_var v = b.get_var(); lp::var_index vi = m_theory_var2var_index[v]; SASSERT(m_solver->is_term(vi)); lp::lar_term const& term = m_solver->get_term(vi); for (auto const & coeff : term.m_coeffs) { lp::var_index wi = coeff.first; lp::constraint_index ci; rational value; bool is_strict; if (m_solver->is_term(wi)) { return false; } if (coeff.second.is_neg() == is_lub) { // -3*x ... <= lub based on lower bound for x. if (!m_solver->has_lower_bound(wi, ci, value, is_strict)) { return false; } if (is_strict) { r += inf_rational(rational::zero(), coeff.second.is_pos()); } } else { if (!m_solver->has_upper_bound(wi, ci, value, is_strict)) { return false; } if (is_strict) { r += inf_rational(rational::zero(), coeff.second.is_pos()); } } r += value * coeff.second; set_evidence(ci); } TRACE("arith_verbose", tout << (is_lub?"lub":"glb") << " is " << r << "\n";); return true; } void assert_bound(bool_var bv, bool is_true, lp_api::bound& b) { if (m_solver->get_status() == lp::lp_status::INFEASIBLE) { return; } scoped_internalize_state st(*this); st.vars().push_back(b.get_var()); st.coeffs().push_back(rational::one()); init_left_side(st); lp::lconstraint_kind k = lp::EQ; bool is_int = b.is_int(); switch (b.get_bound_kind()) { case lp_api::lower_t: k = is_true ? lp::GE : (is_int ? lp::LE : lp::LT); break; case lp_api::upper_t: k = is_true ? lp::LE : (is_int ? lp::GE : lp::GT); break; } if (k == lp::LT || k == lp::LE) { ++m_stats.m_assert_lower; } else { ++m_stats.m_assert_upper; } auto vi = get_var_index(b.get_var()); rational bound = b.get_value(); lp::constraint_index ci; if (is_int && !is_true) { rational bound = b.get_value(false).get_rational(); ci = m_solver->add_var_bound(vi, k, bound); } else { ci = m_solver->add_var_bound(vi, k, b.get_value()); } TRACE("arith", tout << "v" << b.get_var() << "\n";); add_ineq_constraint(ci, literal(bv, !is_true)); propagate_eqs(vi, ci, k, b); } // // fixed equalities. // A fixed equality is inferred if there are two variables v1, v2 whose // upper and lower bounds coincide. // Then the equality v1 == v2 is propagated to the core. // typedef std::pair constraint_bound; vector m_lower_terms; vector m_upper_terms; typedef std::pair value_sort_pair; typedef pair_hash, bool_hash> value_sort_pair_hash; typedef map > value2var; value2var m_fixed_var_table; void propagate_eqs(lp::var_index vi, lp::constraint_index ci, lp::lconstraint_kind k, lp_api::bound& b) { if (propagate_eqs()) { rational const& value = b.get_value(); if (k == lp::GE) { if (set_lower_bound(vi, ci, value) && has_upper_bound(vi, ci, value)) { fixed_var_eh(b.get_var(), value); } } else if (k == lp::LE) { if (set_upper_bound(vi, ci, value) && has_lower_bound(vi, ci, value)) { fixed_var_eh(b.get_var(), value); } } } } bool dump_lemmas() const { return m_arith_params.m_arith_dump_lemmas; } bool propagate_eqs() const { return m_arith_params.m_arith_propagate_eqs && m_num_conflicts < m_arith_params.m_arith_propagation_threshold; } bound_prop_mode propagation_mode() const { return m_num_conflicts < m_arith_params.m_arith_propagation_threshold ? m_arith_params.m_arith_bound_prop : BP_NONE; } unsigned small_lemma_size() const { return m_arith_params.m_arith_small_lemma_size; } bool proofs_enabled() const { return m.proofs_enabled(); } bool use_tableau() const { return lp_params(ctx().get_params()).simplex_strategy() < 2; } bool set_upper_bound(lp::var_index vi, lp::constraint_index ci, rational const& v) { return set_bound(vi, ci, v, false); } bool set_lower_bound(lp::var_index vi, lp::constraint_index ci, rational const& v) { return set_bound(vi, ci, v, true); } bool set_bound(lp::var_index vi, lp::constraint_index ci, rational const& v, bool is_lower) { if (m_solver->is_term(vi)) { lp::var_index ti = m_solver->adjust_term_index(vi); auto& vec = is_lower ? m_lower_terms : m_upper_terms; if (vec.size() <= ti) { vec.resize(ti + 1, constraint_bound(UINT_MAX, rational())); } constraint_bound& b = vec[ti]; if (b.first == UINT_MAX || (is_lower? b.second < v : b.second > v)) { TRACE("arith", tout << "tighter bound " << vi << "\n";); ctx().push_trail(vector_value_trail(vec, ti)); b.first = ci; b.second = v; } return true; } else { TRACE("arith", tout << "not a term " << vi << "\n";); // m_solver already tracks bounds on proper variables, but not on terms. bool is_strict = false; rational b; if (is_lower) { return m_solver->has_lower_bound(vi, ci, b, is_strict) && !is_strict && b == v; } else { return m_solver->has_upper_bound(vi, ci, b, is_strict) && !is_strict && b == v; } } } bool var_has_bound(lp::var_index vi, bool is_lower) { bool is_strict = false; rational b; lp::constraint_index ci; if (is_lower) { return m_solver->has_lower_bound(vi, ci, b, is_strict); } else { return m_solver->has_upper_bound(vi, ci, b, is_strict); } } bool has_upper_bound(lp::var_index vi, lp::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, false); } bool has_lower_bound(lp::var_index vi, lp::constraint_index& ci, rational const& bound) { return has_bound(vi, ci, bound, true); } bool has_bound(lp::var_index vi, lp::constraint_index& ci, rational const& bound, bool is_lower) { if (m_solver->is_term(vi)) { lp::var_index ti = m_solver->adjust_term_index(vi); theory_var v = m_term_index2theory_var.get(ti, null_theory_var); rational val; TRACE("arith", tout << vi << " " << v << "\n";); if (v != null_theory_var && a.is_numeral(get_owner(v), val) && bound == val) { ci = UINT_MAX; return bound == val; } auto& vec = is_lower ? m_lower_terms : m_upper_terms; if (vec.size() > ti) { constraint_bound& b = vec[ti]; ci = b.first; return ci != UINT_MAX && bound == b.second; } else { return false; } } else { bool is_strict = false; rational b; if (is_lower) { return m_solver->has_lower_bound(vi, ci, b, is_strict) && b == bound && !is_strict; } else { return m_solver->has_upper_bound(vi, ci, b, is_strict) && b == bound && !is_strict; } } } bool is_equal(theory_var x, theory_var y) const { return get_enode(x)->get_root() == get_enode(y)->get_root(); } void fixed_var_eh(theory_var v1, rational const& bound) { theory_var v2; value_sort_pair key(bound, is_int(v1)); if (m_fixed_var_table.find(key, v2)) { if (static_cast(v2) < th.get_num_vars() && !is_equal(v1, v2)) { auto vi1 = get_var_index(v1); auto vi2 = get_var_index(v2); lp::constraint_index ci1, ci2, ci3, ci4; TRACE("arith", tout << "fixed: " << mk_pp(get_owner(v1), m) << " " << mk_pp(get_owner(v2), m) << " " << bound << " " << has_lower_bound(vi2, ci3, bound) << "\n";); if (has_lower_bound(vi2, ci3, bound) && has_upper_bound(vi2, ci4, bound)) { VERIFY (has_lower_bound(vi1, ci1, bound)); VERIFY (has_upper_bound(vi1, ci2, bound)); ++m_stats.m_fixed_eqs; m_core.reset(); m_eqs.reset(); set_evidence(ci1); set_evidence(ci2); set_evidence(ci3); set_evidence(ci4); enode* x = get_enode(v1); enode* y = get_enode(v2); justification* js = ctx().mk_justification( ext_theory_eq_propagation_justification( get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), x, y, 0, nullptr)); TRACE("arith", for (literal c : m_core) { ctx().display_detailed_literal(tout, c); tout << "\n"; } for (enode_pair const& p : m_eqs) { tout << enode_eq_pp(p, ctx()); } tout << " ==> "; tout << enode_pp(x, ctx()) << " = " << enode_pp(y, ctx()) << "\n"; ); // parameters are TBD. // SASSERT(validate_eq(x, y)); ctx().assign_eq(x, y, eq_justification(js)); } } else { // bounds on v2 were changed. m_fixed_var_table.insert(key, v1); } } else { m_fixed_var_table.insert(key, v1); } } lbool make_feasible() { auto status = m_solver->find_feasible_solution(); TRACE("arith_verbose", display(tout);); switch (status) { case lp::lp_status::INFEASIBLE: return l_false; case lp::lp_status::FEASIBLE: case lp::lp_status::OPTIMAL: // SASSERT(m_solver->all_constraints_hold()); return l_true; case lp::lp_status::TIME_EXHAUSTED: default: TRACE("arith", tout << "status treated as inconclusive: " << status << "\n";); // TENTATIVE_UNBOUNDED, UNBOUNDED, TENTATIVE_DUAL_UNBOUNDED, DUAL_UNBOUNDED, // FLOATING_POINT_ERROR, TIME_EXAUSTED, ITERATIONS_EXHAUSTED, EMPTY, UNSTABLE return l_undef; } } vector> m_explanation; literal_vector m_core; svector m_eqs; vector m_params; // lp::constraint_index const null_constraint_index = UINT_MAX; // not sure what a correct fix is void set_evidence(lp::constraint_index idx) { if (idx == UINT_MAX) { return; } switch (m_constraint_sources[idx]) { case inequality_source: { literal lit = m_inequalities[idx]; SASSERT(lit != null_literal); m_core.push_back(lit); break; } case equality_source: { SASSERT(m_equalities[idx].first != nullptr); SASSERT(m_equalities[idx].second != nullptr); m_eqs.push_back(m_equalities[idx]); break; } case definition_source: { // skip definitions (these are treated as hard constraints) break; } default: UNREACHABLE(); break; } } void set_conflict() { m_explanation.clear(); m_solver->get_infeasibility_explanation(m_explanation); set_conflict1(); } void set_conflict1() { m_eqs.reset(); m_core.reset(); m_params.reset(); // m_solver->shrink_explanation_to_minimum(m_explanation); // todo, enable when perf is fixed /* static unsigned cn = 0; static unsigned num_l = 0; num_l+=m_explanation.size(); std::cout << num_l / (++cn) << "\n"; */ ++m_num_conflicts; ++m_stats.m_conflicts; TRACE("arith", tout << "scope: " << ctx().get_scope_level() << "\n"; display_evidence(tout, m_explanation); ); TRACE("arith", display(tout);); for (auto const& ev : m_explanation) { if (!ev.first.is_zero()) { set_evidence(ev.second); } } // SASSERT(validate_conflict()); dump_conflict(); ctx().set_conflict( ctx().mk_justification( ext_theory_conflict_justification( get_id(), ctx().get_region(), m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), m_params.size(), m_params.c_ptr()))); } justification * why_is_diseq(theory_var v1, theory_var v2) { return nullptr; } void reset_eh() { m_use_nra_model = false; m_arith_eq_adapter.reset_eh(); m_solver = nullptr; m_internalize_head = 0; m_not_handled = nullptr; del_bounds(0); m_unassigned_bounds.reset(); m_asserted_qhead = 0; m_assume_eq_head = 0; m_scopes.reset(); m_stats.reset(); m_to_check.reset(); reset_variable_values(); } void init_model(model_generator & mg) { init_variable_values(); m_factory = alloc(arith_factory, m); mg.register_factory(m_factory); TRACE("arith", display(tout);); } nlsat::anum const& nl_value(theory_var v, scoped_anum& r) { SASSERT(m_nra); SASSERT(m_use_nra_model); lp::var_index vi = m_theory_var2var_index[v]; if (m_solver->is_term(vi)) { m_todo_terms.push_back(std::make_pair(vi, rational::one())); TRACE("arith", tout << "v" << v << " := w" << vi << "\n"; m_solver->print_term(m_solver->get_term(vi), tout) << "\n";); m_nra->am().set(r, 0); while (!m_todo_terms.empty()) { rational wcoeff = m_todo_terms.back().second; vi = m_todo_terms.back().first; m_todo_terms.pop_back(); lp::lar_term const& term = m_solver->get_term(vi); TRACE("arith", m_solver->print_term(term, tout) << "\n";); scoped_anum r1(m_nra->am()); rational c1(0); m_nra->am().set(r1, c1.to_mpq()); m_nra->am().add(r, r1, r); for (auto const & arg : term) { lp::var_index wi = arg.var(); c1 = arg.coeff() * wcoeff; if (m_solver->is_term(wi)) { m_todo_terms.push_back(std::make_pair(wi, c1)); } else { m_nra->am().set(r1, c1.to_mpq()); m_nra->am().mul(m_nra->value(wi), r1, r1); m_nra->am().add(r1, r, r); } } } return r; } else { return m_nra->value(vi); } } model_value_proc * mk_value(enode * n, model_generator & mg) { theory_var v = n->get_th_var(get_id()); expr* o = n->get_owner(); if (m_use_nra_model) { anum const& an = nl_value(v, *m_a1); if (a.is_int(o) && !m_nra->am().is_int(an)) { return alloc(expr_wrapper_proc, a.mk_numeral(rational::zero(), a.is_int(o))); } return alloc(expr_wrapper_proc, a.mk_numeral(nl_value(v, *m_a1), a.is_int(o))); } else { rational r = get_value(v); TRACE("arith", tout << "v" << v << " := " << r << "\n";); SASSERT(!a.is_int(o) || r.is_int()); if (a.is_int(o) && !r.is_int()) r = floor(r); return alloc(expr_wrapper_proc, m_factory->mk_value(r, m.get_sort(o))); } } bool get_value(enode* n, rational& val) { theory_var v = n->get_th_var(get_id()); if (!can_get_bound(v)) return false; lp::var_index vi = m_theory_var2var_index[v]; if (m_solver->has_value(vi, val)) { TRACE("arith", tout << expr_ref(n->get_owner(), m) << " := " << val << "\n";); if (is_int(n) && !val.is_int()) return false; return true; } else { return false; } } bool get_value(enode* n, expr_ref& r) { rational val; if (get_value(n, val)) { r = a.mk_numeral(val, is_int(n)); return true; } else { return false; } } bool get_lower(enode* n, rational& val, bool& is_strict) { theory_var v = n->get_th_var(get_id()); if (!can_get_bound(v)) { TRACE("arith", tout << "cannot get lower for " << v << "\n";); return false; } lp::var_index vi = m_theory_var2var_index[v]; lp::constraint_index ci; return m_solver->has_lower_bound(vi, ci, val, is_strict); } bool get_lower(enode* n, expr_ref& r) { bool is_strict; rational val; if (get_lower(n, val, is_strict) && !is_strict) { r = a.mk_numeral(val, is_int(n)); return true; } return false; } bool get_upper(enode* n, rational& val, bool& is_strict) { theory_var v = n->get_th_var(get_id()); if (!can_get_bound(v)) return false; lp::var_index vi = m_theory_var2var_index[v]; lp::constraint_index ci; return m_solver->has_upper_bound(vi, ci, val, is_strict); } bool get_upper(enode* n, expr_ref& r) { bool is_strict; rational val; if (get_upper(n, val, is_strict) && !is_strict) { r = a.mk_numeral(val, is_int(n)); return true; } return false; } // Auxiliary verification utilities. struct scoped_arith_mode { smt_params& p; scoped_arith_mode(smt_params& p) : p(p) { p.m_arith_mode = AS_OLD_ARITH; } ~scoped_arith_mode() { p.m_arith_mode = AS_NEW_ARITH; } }; void dump_conflict() { if (dump_lemmas()) { unsigned id = ctx().display_lemma_as_smt_problem(m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), false_literal); (void)id; } } bool validate_conflict() { if (m_arith_params.m_arith_mode != AS_NEW_ARITH) return true; scoped_arith_mode _sa(ctx().get_fparams()); context nctx(m, ctx().get_fparams(), ctx().get_params()); add_background(nctx); cancel_eh eh(m.limit()); scoped_timer timer(1000, &eh); bool result = l_true != nctx.check(); CTRACE("arith", !result, ctx().display_lemma_as_smt_problem(tout, m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), false_literal);); return result; } void dump_assign(literal lit) { if (dump_lemmas()) { unsigned id = ctx().display_lemma_as_smt_problem(m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); (void)id; } } bool validate_assign(literal lit) { if (m_arith_params.m_arith_mode != AS_NEW_ARITH) return true; scoped_arith_mode _sa(ctx().get_fparams()); context nctx(m, ctx().get_fparams(), ctx().get_params()); m_core.push_back(~lit); add_background(nctx); m_core.pop_back(); cancel_eh eh(m.limit()); scoped_timer timer(1000, &eh); bool result = l_true != nctx.check(); CTRACE("arith", !result, ctx().display_lemma_as_smt_problem(tout, m_core.size(), m_core.c_ptr(), m_eqs.size(), m_eqs.c_ptr(), lit); display(tout);); return result; } bool validate_eq(enode* x, enode* y) { if (m_arith_params.m_arith_mode == AS_NEW_ARITH) return true; context nctx(m, ctx().get_fparams(), ctx().get_params()); add_background(nctx); nctx.assert_expr(m.mk_not(m.mk_eq(x->get_owner(), y->get_owner()))); cancel_eh eh(m.limit()); scoped_timer timer(1000, &eh); return l_true != nctx.check(); } void add_background(context& nctx) { for (literal c : m_core) { expr_ref tmp(m); ctx().literal2expr(c, tmp); nctx.assert_expr(tmp); } for (auto const& eq : m_eqs) { nctx.assert_expr(m.mk_eq(eq.first->get_owner(), eq.second->get_owner())); } } theory_lra::inf_eps value(theory_var v) { lp::impq ival = get_ivalue(v); return inf_eps(rational(0), inf_rational(ival.x, ival.y)); } theory_lra::inf_eps maximize(theory_var v, expr_ref& blocker, bool& has_shared) { lp::impq term_max; lp::lp_status st; lp::var_index vi = 0; if (!can_get_bound(v)) { TRACE("arith", tout << "cannot get bound for v" << v << "\n";); st = lp::lp_status::UNBOUNDED; } else { vi = m_theory_var2var_index[v]; st = m_solver->maximize_term(vi, term_max); } switch (st) { case lp::lp_status::OPTIMAL: { init_variable_values(); TRACE("arith", display(tout << st << " v" << v << " vi: " << vi << "\n");); inf_rational val = get_value(v); // inf_rational val(term_max.x, term_max.y); blocker = mk_gt(v); return inf_eps(rational::zero(), val); } case lp::lp_status::FEASIBLE: { inf_rational val = get_value(v); TRACE("arith", display(tout << st << " v" << v << " vi: " << vi << "\n");); blocker = mk_gt(v); return inf_eps(rational::zero(), val); } default: SASSERT(st == lp::lp_status::UNBOUNDED); TRACE("arith", display(tout << st << " v" << v << " vi: " << vi << "\n");); has_shared = false; blocker = m.mk_false(); return inf_eps(rational::one(), inf_rational()); } } expr_ref mk_gt(theory_var v) { lp::impq val = get_ivalue(v); expr* obj = get_enode(v)->get_owner(); rational r = val.x; expr_ref e(m); if (a.is_int(m.get_sort(obj))) { if (r.is_int()) { r += rational::one(); } else { r = ceil(r); } e = a.mk_numeral(r, m.get_sort(obj)); e = a.mk_ge(obj, e); } else { e = a.mk_numeral(r, m.get_sort(obj)); if (val.y.is_neg()) { e = a.mk_ge(obj, e); } else { e = a.mk_gt(obj, e); } } TRACE("opt", tout << "v" << v << " " << val << " " << r << " " << e << "\n";); return e; } theory_var add_objective(app* term) { TRACE("opt", tout << expr_ref(term, m) << "\n";); return internalize_def(term); } void term2coeffs(lp::lar_term const& term, u_map& coeffs, rational const& coeff, rational& offset) { for (const auto & ti : term) { theory_var w; if (m_solver->is_term(ti.var())) { //w = m_term_index2theory_var.get(m_solver->adjust_term_index(ti.var()), null_theory_var); //if (w == null_theory_var) // if extracting expressions directly from nested term lp::lar_term const& term1 = m_solver->get_term(ti.var()); rational coeff2 = coeff * ti.coeff(); term2coeffs(term1, coeffs, coeff2, offset); continue; } else { w = m_var_index2theory_var[ti.var()]; } rational c0(0); coeffs.find(w, c0); coeffs.insert(w, c0 + ti.coeff() * coeff); } } app_ref coeffs2app(u_map const& coeffs, rational const& offset, bool is_int) { expr_ref_vector args(m); for (auto const& kv : coeffs) { theory_var w = kv.m_key; expr* o = get_enode(w)->get_owner(); if (kv.m_value.is_zero()) { // continue } else if (kv.m_value.is_one()) { args.push_back(o); } else { args.push_back(a.mk_mul(a.mk_numeral(kv.m_value, is_int), o)); } } if (!offset.is_zero()) { args.push_back(a.mk_numeral(offset, is_int)); } switch (args.size()) { case 0: return app_ref(a.mk_numeral(rational::zero(), is_int), m); case 1: return app_ref(to_app(args[0].get()), m); default: return app_ref(a.mk_add(args.size(), args.c_ptr()), m); } } app_ref mk_term(lp::lar_term const& term, bool is_int) { u_map coeffs; rational offset; term2coeffs(term, coeffs, rational::one(), offset); return coeffs2app(coeffs, offset, is_int); } rational gcd_reduce(u_map& coeffs) { rational g(0); for (auto const& kv : coeffs) { g = gcd(g, kv.m_value); } if (g.is_zero()) return rational::one(); if (!g.is_one()) { for (auto& kv : coeffs) { kv.m_value /= g; } } return g; } app_ref mk_obj(theory_var v) { lp::var_index vi = get_var_index(v); bool is_int = a.is_int(get_enode(v)->get_owner()); if (m_solver->is_term(vi)) { return mk_term(m_solver->get_term(vi), is_int); } else { theory_var w = m_var_index2theory_var[vi]; return app_ref(get_enode(w)->get_owner(), m); } } expr_ref mk_ge(generic_model_converter& fm, theory_var v, inf_rational const& val) { rational r = val.get_rational(); bool is_strict = val.get_infinitesimal().is_pos(); app_ref b(m); bool is_int = a.is_int(get_enode(v)->get_owner()); if (is_strict) { b = a.mk_le(mk_obj(v), a.mk_numeral(r, is_int)); } else { b = a.mk_ge(mk_obj(v), a.mk_numeral(r, is_int)); } if (!ctx().b_internalized(b)) { fm.hide(b->get_decl()); bool_var bv = ctx().mk_bool_var(b); ctx().set_var_theory(bv, get_id()); // ctx().set_enode_flag(bv, true); lp_api::bound_kind bkind = lp_api::bound_kind::lower_t; if (is_strict) bkind = lp_api::bound_kind::upper_t; lp_api::bound* a = alloc(lp_api::bound, bv, v, is_int, r, bkind); mk_bound_axioms(*a); updt_unassigned_bounds(v, +1); m_bounds[v].push_back(a); m_bounds_trail.push_back(v); m_bool_var2bound.insert(bv, a); TRACE("arith", tout << mk_pp(b, m) << "\n";); } if (is_strict) { b = m.mk_not(b); } TRACE("arith", tout << b << "\n";); return expr_ref(b, m); } void display(std::ostream & out) const { if (m_solver) { m_solver->print_constraints(out); m_solver->print_terms(out); // auto pp = lp ::core_solver_pretty_printer(m_solver->m_mpq_lar_core_solver.m_r_solver, out); // pp.print(); } unsigned nv = th.get_num_vars(); for (unsigned v = 0; v < nv; ++v) { if (!ctx().is_relevant(get_enode(v))) out << "irr: "; out << "v" << v; if (can_get_value(v)) out << " = " << get_value(v); if (is_int(v)) out << ", int"; if (ctx().is_shared(get_enode(v))) out << ", shared"; out << " := "; th.display_var_flat_def(out, v) << "\n"; } } void display_evidence(std::ostream& out, vector> const& evidence) { for (auto const& ev : evidence) { expr_ref e(m); SASSERT(!ev.first.is_zero()); if (ev.first.is_zero()) { continue; } unsigned idx = ev.second; switch (m_constraint_sources.get(idx, null_source)) { case inequality_source: { literal lit = m_inequalities[idx]; ctx().literal2expr(lit, e); out << e << " " << ctx().get_assignment(lit) << "\n"; break; } case equality_source: out << enode_eq_pp(m_equalities[idx], ctx()); break; case definition_source: { theory_var v = m_definitions[idx]; if (v != null_theory_var) out << "def: v" << v << " := " << mk_pp(th.get_enode(v)->get_owner(), m) << "\n"; break; } case null_source: default: UNREACHABLE(); break; } } for (auto const& ev : evidence) { m_solver->print_constraint(ev.second, out << ev.first << ": "); } } void collect_statistics(::statistics & st) const { m_arith_eq_adapter.collect_statistics(st); st.update("arith-lower", m_stats.m_assert_lower); st.update("arith-upper", m_stats.m_assert_upper); st.update("arith-rows", m_stats.m_add_rows); st.update("arith-propagations", m_stats.m_bounds_propagations); st.update("arith-iterations", m_stats.m_num_iterations); st.update("arith-factorizations", m_solver->settings().st().m_num_factorizations); st.update("arith-pivots", m_stats.m_need_to_solve_inf); st.update("arith-plateau-iterations", m_stats.m_num_iterations_with_no_progress); st.update("arith-fixed-eqs", m_stats.m_fixed_eqs); st.update("arith-conflicts", m_stats.m_conflicts); st.update("arith-bound-propagations-lp", m_stats.m_bound_propagations1); st.update("arith-bound-propagations-cheap", m_stats.m_bound_propagations2); st.update("arith-diseq", m_stats.m_assert_diseq); st.update("arith-make-feasible", m_solver->settings().st().m_make_feasible); st.update("arith-max-columns", m_solver->settings().st().m_max_cols); st.update("arith-max-rows", m_solver->settings().st().m_max_rows); st.update("gcd-calls", m_solver->settings().st().m_gcd_calls); st.update("gcd-conflict", m_solver->settings().st().m_gcd_conflicts); st.update("cube-calls", m_solver->settings().st().m_cube_calls); st.update("cube-success", m_solver->settings().st().m_cube_success); st.update("arith-patches", m_solver->settings().st().m_patches); st.update("arith-patches-success", m_solver->settings().st().m_patches_success); st.update("arith-hnf-calls", m_solver->settings().st().m_hnf_cutter_calls); st.update("arith-hnf-cuts", m_solver->settings().st().m_hnf_cuts); } }; theory_lra::theory_lra(ast_manager& m, theory_arith_params& ap): theory(m.get_family_id("arith")) { m_imp = alloc(imp, *this, m, ap); } theory_lra::~theory_lra() { dealloc(m_imp); } theory* theory_lra::mk_fresh(context* new_ctx) { return alloc(theory_lra, new_ctx->get_manager(), new_ctx->get_fparams()); } void theory_lra::init(context * ctx) { theory::init(ctx); m_imp->init(ctx); } bool theory_lra::internalize_atom(app * atom, bool gate_ctx) { return m_imp->internalize_atom(atom, gate_ctx); } bool theory_lra::internalize_term(app * term) { return m_imp->internalize_term(term); } void theory_lra::internalize_eq_eh(app * atom, bool_var v) { m_imp->internalize_eq_eh(atom, v); } void theory_lra::assign_eh(bool_var v, bool is_true) { m_imp->assign_eh(v, is_true); } lbool theory_lra::get_phase(bool_var v) { return m_imp->get_phase(v); } void theory_lra::new_eq_eh(theory_var v1, theory_var v2) { m_imp->new_eq_eh(v1, v2); } bool theory_lra::use_diseqs() const { return m_imp->use_diseqs(); } void theory_lra::new_diseq_eh(theory_var v1, theory_var v2) { m_imp->new_diseq_eh(v1, v2); } void theory_lra::push_scope_eh() { theory::push_scope_eh(); m_imp->push_scope_eh(); } void theory_lra::pop_scope_eh(unsigned num_scopes) { m_imp->pop_scope_eh(num_scopes); theory::pop_scope_eh(num_scopes); } void theory_lra::restart_eh() { m_imp->restart_eh(); } void theory_lra::relevant_eh(app* e) { m_imp->relevant_eh(e); } void theory_lra::init_search_eh() { m_imp->init_search_eh(); } final_check_status theory_lra::final_check_eh() { return m_imp->final_check_eh(); } bool theory_lra::is_shared(theory_var v) const { return m_imp->is_shared(v); } bool theory_lra::can_propagate() { return m_imp->can_propagate(); } void theory_lra::propagate() { m_imp->propagate(); } justification * theory_lra::why_is_diseq(theory_var v1, theory_var v2) { return m_imp->why_is_diseq(v1, v2); } void theory_lra::reset_eh() { m_imp->reset_eh(); } void theory_lra::init_model(model_generator & m) { m_imp->init_model(m); } model_value_proc * theory_lra::mk_value(enode * n, model_generator & mg) { return m_imp->mk_value(n, mg); } bool theory_lra::get_value(enode* n, rational& r) { return m_imp->get_value(n, r); } bool theory_lra::get_value(enode* n, expr_ref& r) { return m_imp->get_value(n, r); } bool theory_lra::get_lower(enode* n, expr_ref& r) { return m_imp->get_lower(n, r); } bool theory_lra::get_upper(enode* n, expr_ref& r) { return m_imp->get_upper(n, r); } bool theory_lra::get_lower(enode* n, rational& r, bool& is_strict) { return m_imp->get_lower(n, r, is_strict); } bool theory_lra::get_upper(enode* n, rational& r, bool& is_strict) { return m_imp->get_upper(n, r, is_strict); } void theory_lra::display(std::ostream & out) const { m_imp->display(out); } void theory_lra::collect_statistics(::statistics & st) const { m_imp->collect_statistics(st); } theory_lra::inf_eps theory_lra::value(theory_var v) { return m_imp->value(v); } theory_lra::inf_eps theory_lra::maximize(theory_var v, expr_ref& blocker, bool& has_shared) { return m_imp->maximize(v, blocker, has_shared); } theory_var theory_lra::add_objective(app* term) { return m_imp->add_objective(term); } expr_ref theory_lra::mk_ge(generic_model_converter& fm, theory_var v, inf_rational const& val) { return m_imp->mk_ge(fm, v, val); } }