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