From fba22d2facf66409602ca82d7a8477db1226de3d Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Mon, 6 Nov 2017 15:32:02 +0100 Subject: [PATCH 01/65] design document for handling recursive functions --- doc/design_recfuns.md | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 doc/design_recfuns.md diff --git a/doc/design_recfuns.md b/doc/design_recfuns.md new file mode 100644 index 000000000..9dbe5c874 --- /dev/null +++ b/doc/design_recfuns.md @@ -0,0 +1,85 @@ +# Design for handling recursive functions + +Main source of inspiration is [Sutter, Köksal & Kuncak 2011], +as implemented in Leon, but the main +differences is that we should unroll function definitions directly from the +inside of Z3, in a backtracking way. Termination and fairness are ensured by +iterative-deepening on the maximum number of unrollings in a given branch. + +## Unfolding + +The idea is that every function definition `f(x1…xn) := rhs[x1…xn]` is +compiled into: + +- a list of cases `A_f_i[x1…xn] => f(x1…xn) = rhs_i[x1…xn]`. + When `A_f_i[t1…tn]` becomes true in the model, `f(t1…tn)` is said to be + *unfolded* and the clause `A_f_i[t1…tn] => f(t1…tn) = rhs_i[t1…tn]` + is added as an auxiliary clause. +- a list of constraints `Γ_f_i[x1…xn] <=> A_f_i[x1…xn]` + that states when `A_f_i[x1…xn]` should be true, depending on inputs `x1…xn`. + For every term `f(t1…tn)` present in congruence closure, we + immediately add all the `Γ_f_i[t1…tn] <=> A_f_i[t1…tn]` as auxiliary clauses + (maybe during internalization of `f(t1…tn)`?). + +where each `A_f_i[x1…xn]` is a special new predicate representing the +given case of `f`, and `rhs_i` does not contain any `ite`. +We assume pattern matching has been compiled to `ite` beforehand. + +For example, `fact(n) := if n<2 then 1 else n * fact(n-1)` is compiled into: + +- `A_fact_0[n] => fact(n) = 1` +- `A_fact_1[n] => fact(n) = n * fact(n-1)` +- `A_fact_0[n] <=> n < 2` +- `A_fact_1[n] <=> ¬(n < 2)` + +The 2 first clauses are only added when `A_fact_0[t]` is true +(respectively `A_fact_1[t]` is true). +The 2 other clauses are added as soon as `fact(t)` is internalized. + +## Termination + +To ensure termination, we define variables: + +- `unfold_depth: int` +- `current_max_unfold_depth: int` +- `global_max_unfold_depth: int` + +and a special literal `[max_depth=$n]` for each `n:int`. +Solving is done under the local assumption +`[max_depth=$current_max_unfold_depth]` (this should be handled in some outer +loop, e.g. in a custom tactic). + +Whenever `A_f_i[t1…tn]` becomes true (for any `f`), we increment +`unfold_depth`. If `unfold_depth > current_max_unfold_depth`, then +the conflict clause `[max_depth=$current_max_unfold_depth] => Γ => false` +where `Γ` is the conjunction of all `A_f_i[t1…tn]` true in the trail. + +For non-recursive functions, we don't have to increment `unfold_depth`. Some other functions that are known + +If the solver answers "SAT", we have a model. +Otherwise, if `[max_depth=$current_max_unfold_depth]` is part of the +unsat-core, then we increase `current_max_unfold_depth`. +If `current_max_unfold_depth == global_max_unfold_depth` then +we report "UNKNOWN" (reached global depth limit), otherwise we can +try to `solve()` again with the new assumption (higher depth limit). + +## Tactic + +there should be a parametrized tactic `funrec(t, n)` where `t` is the tactic +used to solve (under assumption that depth is limited to `current_max_unfold_depth`) +and `n` is an integer that is assigned to `global_max_unfold_depth`. + +This way, to try and find models for a problem with recursive functions + LIA, +one could use something like `(funrec (then simplify dl smt) 100)`. + +## Expected benefits + +This addition to Z3 would bring many benefits compared to current alternatives (Leon, quantifiers, …) + +- should be very fast and lightweight + (compared to Leon or quantifiers). + In particular, every function call is very lightweight even compared to Leon (no need for full model building, followed by unsat core extraction) +- possibility of answering "SAT" for any `QF_*` fragment + + recursive functions +- makes `define-funs-rec` a first-class citizen of the language, usable to model user-defined theories or to analyze functional + programs directly From d5e134dd948c103d8883afe9398f7dc31a398ed5 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Tue, 7 Nov 2017 15:57:27 +0100 Subject: [PATCH 02/65] wip: add recursive functions --- doc/design_recfuns.md | 8 + src/ast/CMakeLists.txt | 1 + src/ast/ast_pp.h | 22 ++ src/ast/recfun_decl_plugin.cpp | 469 +++++++++++++++++++++++++++ src/ast/recfun_decl_plugin.h | 246 ++++++++++++++ src/ast/reg_decl_plugins.cpp | 4 + src/cmd_context/cmd_context.cpp | 48 ++- src/cmd_context/cmd_context.h | 5 + src/parsers/smt2/smt2parser.cpp | 6 +- src/smt/CMakeLists.txt | 1 + src/smt/params/smt_params.cpp | 1 + src/smt/params/smt_params.h | 4 + src/smt/params/smt_params_helper.pyg | 3 +- src/smt/smt_setup.cpp | 10 + src/smt/smt_setup.h | 1 + src/smt/theory_recfun.cpp | 363 +++++++++++++++++++++ src/smt/theory_recfun.h | 155 +++++++++ src/util/scoped_ptr_vector.h | 8 + src/util/trail.h | 11 + 19 files changed, 1362 insertions(+), 4 deletions(-) create mode 100644 src/ast/recfun_decl_plugin.cpp create mode 100644 src/ast/recfun_decl_plugin.h create mode 100644 src/smt/theory_recfun.cpp create mode 100644 src/smt/theory_recfun.h diff --git a/doc/design_recfuns.md b/doc/design_recfuns.md index 9dbe5c874..89980931e 100644 --- a/doc/design_recfuns.md +++ b/doc/design_recfuns.md @@ -83,3 +83,11 @@ This addition to Z3 would bring many benefits compared to current alternatives ( recursive functions - makes `define-funs-rec` a first-class citizen of the language, usable to model user-defined theories or to analyze functional programs directly + +## Optimizations + +- maybe `C_f_i` literals should never be decided on + (they can always be propagated). + Even stronger: they should not be part of conflicts? + (i.e. tune conflict resolution to always resolve + these literals away, disregarding their level) diff --git a/src/ast/CMakeLists.txt b/src/ast/CMakeLists.txt index 4dcdd2a35..f452c07d5 100644 --- a/src/ast/CMakeLists.txt +++ b/src/ast/CMakeLists.txt @@ -36,6 +36,7 @@ z3_add_component(ast occurs.cpp pb_decl_plugin.cpp pp.cpp + recfun_decl_plugin.cpp reg_decl_plugins.cpp seq_decl_plugin.cpp shared_occs.cpp diff --git a/src/ast/ast_pp.h b/src/ast/ast_pp.h index 997b1a6e0..514aba32f 100644 --- a/src/ast/ast_pp.h +++ b/src/ast/ast_pp.h @@ -32,5 +32,27 @@ struct mk_pp : public mk_ismt2_pp { } }; +// +#include +#include "ast/expr_functors.h" +#include "ast/expr_substitution.h" +#include "ast/recfun_decl_plugin.h" +#include "ast/ast_pp.h" +#include "util/scoped_ptr_vector.h" + +#define DEBUG(x) do { auto& out = std::cout; out << "recfun: "; x; out << '\n' << std::flush; } while(0) +#define VALIDATE_PARAM(m, _pred_) if (!(_pred_)) m.raise_exception("invalid parameter to recfun " #_pred_); + + +namespace recfun { + case_pred::case_pred(ast_manager & m, family_id fid, std::string const & s, sort_ref_vector const & domain) + : m_name(), m_name_buf(s), m_domain(domain), m_decl(m) + { + m_name = symbol(m_name_buf.c_str()); + func_decl_info info(fid, OP_FUN_CASE_PRED); + m_decl = m.mk_func_decl(m_name, domain.size(), domain.c_ptr(), m.mk_bool_sort(), info); + } + + case_def::case_def(ast_manager &m, + family_id fid, + def * d, + std::string & name, + sort_ref_vector const & arg_sorts, + unsigned num_guards, expr ** guards, expr* rhs) + : m_pred(m, fid, name, arg_sorts), m_guards(m), m_rhs(expr_ref(rhs,m)), m_def(d) { + for (unsigned i=0; i m_branches; + + public: + case_state(ast_manager & m) : m_reg(), m_manager(m), m_branches() {} + + bool empty() const { return m_branches.empty(); } + ast_manager & m() const { return m_manager; } + region & reg() { return m_reg; } + + branch pop_branch() { + branch res = m_branches.back(); + m_branches.pop_back(); + return res; + } + + void push_branch(branch const & b) { m_branches.push_back(b); } + + + unfold_lst const * cons_unfold(expr * e, unfold_lst const * next) { + return new (reg()) unfold_lst{e, next}; + } + unfold_lst const * cons_unfold(expr * e1, expr * e2, unfold_lst const * next) { + return cons_unfold(e1, cons_unfold(e2, next)); + } + unfold_lst const * mk_unfold_lst(expr * e) { + return cons_unfold(e, nullptr); + } + + ite_lst const * cons_ite(app * ite, ite_lst const * next) { + return new (reg()) ite_lst{ite, next}; + } + + choice_lst const * cons_choice(app * ite, bool sign, choice_lst const * next) { + return new (reg()) choice_lst{ite, sign, next}; + } + }; + + //next) { + app * ite = choices->ite; + SASSERT(m.is_ite(ite)); + + // condition to add to the guard + expr * cond0 = ite->get_arg(0); + conditions.push_back(choices->sign ? cond0 : m.mk_not(cond0)); + + // binding to add to the substitution + subst.insert(ite, choices->sign ? ite->get_arg(1) : ite->get_arg(2)); + } + } + + // substitute `subst` in `e` + expr_ref replace_subst(th_rewriter & th_rw, ast_manager & m, + expr_substitution & subst, expr * e) { + th_rw.reset(); + th_rw.set_substitution(&subst); + expr_ref res(m); + th_rw(e, res); + return res; + } + + void def::add_case(std::string & name, unsigned n_conditions, expr ** conditions, expr * rhs, bool is_imm) { + case_def c(m(), m_fid, this, name, get_domain(), n_conditions, conditions, rhs); + c.set_is_immediate(is_imm); + TRACE("recfun", tout << "add_case " << name << " " << mk_pp(rhs, m()) + << " :is_imm " << is_imm + << " :guards " << mk_pp_vec(n_conditions, (ast**)conditions, m());); + DEBUG(out << "add_case " << name << " " << mk_pp(rhs, m()) + << " :is_imm " << is_imm + << " :guards " << mk_pp_vec(n_conditions, (ast**)conditions, m())); + m_cases.push_back(c); + } + + + // Compute a set of cases, given the RHS + void def::compute_cases(is_immediate_pred & is_i, th_rewriter & th_rw, + unsigned n_vars, var *const * vars, expr* rhs0) + { + if (m_cases.size() != 0) { + TRACE("recfun", tout << "bug: cases for " << m_name << " has cases already";); + UNREACHABLE(); + } + SASSERT(n_vars = m_domain.size()); + + DEBUG(out << "compute cases " << mk_pp(rhs0, m())); + + unsigned case_idx = 0; + std::string name; + + name.append("case_"); + name.append(m_name.bare_str()); + name.append("_"); + + for (unsigned i=0; i stack; + stack.push_back(b.to_unfold->e); + + b.to_unfold = b.to_unfold->next; + + while (! stack.empty()) { + expr * e = stack.back(); + stack.pop_back(); + + if (m().is_ite(e)) { + // need to do a case split on `e`, forking the search space + b.to_split = st.cons_ite(to_app(e), b.to_split); + } else if (is_app(e)) { + // explore arguments + app * a = to_app(e); + + for (unsigned i=0; i < a->get_num_args(); ++i) + stack.push_back(a->get_arg(i)); + } + } + } + + if (b.to_split != nullptr) { + // split one `ite`, which will lead to distinct (sets of) cases + app * ite = b.to_split->ite; + SASSERT(m().is_ite(ite)); + + /* explore both positive choice and negative choice. + * each contains a longer path, with `ite` mapping to `true` (resp. `false), + * and must unfold the `then` (resp. `else`) branch. + * We must also unfold the test itself, for it could contain + * tests. + */ + + branch b_pos(st.cons_choice(ite, true, b.path), + b.to_split->next, + st.cons_unfold(ite->get_arg(0), ite->get_arg(1), b.to_unfold)); + branch b_neg(st.cons_choice(ite, false, b.path), + b.to_split->next, + st.cons_unfold(ite->get_arg(0), ite->get_arg(2), b.to_unfold)); + + st.push_branch(b_neg); + st.push_branch(b_pos); + } + else { + // leaf of the search tree + + expr_ref_vector conditions_raw(m()); + expr_substitution subst(m()); + convert_path(m(), b.path, conditions_raw, subst); + + // substitute, to get rid of `ite` terms + expr_ref case_rhs = replace_subst(th_rw, m(), subst, rhs); + expr_ref_vector conditions(m()); + for (expr * g : conditions_raw) { + expr_ref g_subst(replace_subst(th_rw, m(), subst, g), m()); + conditions.push_back(g_subst); + } + + + unsigned old_name_len = name.size(); + { // TODO: optimize? this does many copies + std::ostringstream sout; + sout << ((unsigned long) case_idx); + name.append(sout.str()); + } + case_idx ++; + + // yield new case + bool is_imm = is_i(case_rhs); + add_case(name, conditions.size(), conditions.c_ptr(), case_rhs, is_imm); + name.resize(old_name_len); + } + } + + TRACE("recfun", tout << "done analysing " << get_name();); + } + + /* + * Main manager for defined functions + */ + + util::util(ast_manager & m, family_id id) + : m_manager(m), m_family_id(id), m_th_rw(m), m_plugin(0) { + m_plugin = dynamic_cast(m.get_plugin(m_family_id)); + } + + def * util::decl_fun(symbol const& name, unsigned n, sort *const * domain, sort * range) { + return alloc(def, m(), m_family_id, name, n, domain, range); + } + + void util::set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs) { + d.set_definition(n_vars, vars, rhs); + } + + + // used to know which `app` are from this theory + struct is_imm_pred : is_immediate_pred { + util & u; + is_imm_pred(util & u) : u(u) {} + bool operator()(expr * rhs) { + // find an `app` that is an application of a defined function + struct find : public i_expr_pred { + util & u; + find(util & u) : u(u) {} + bool operator()(expr * e) override { + //return is_app(e) ? u.owns_app(to_app(e)) : false; + if (! is_app(e)) return false; + + app * a = to_app(e); + return u.is_defined(a); + } + }; + find f(u); + check_pred cp(f, u.m()); + bool contains_defined_fun = cp(rhs); + return ! contains_defined_fun; + } + }; + + // set definition + void promise_def::set_definition(unsigned n_vars, var * const * vars, expr * rhs) { + SASSERT(n_vars == d->get_arity()); + + is_imm_pred is_i(*u); + d->compute_cases(is_i, u->get_th_rewriter(), n_vars, vars, rhs); + } + + namespace decl { + plugin::plugin() : decl_plugin(), m_defs(), m_case_defs(), m_def_block() {} + plugin::~plugin() { finalize(); } + + void plugin::finalize() { + for (auto& kv : m_defs) { + dealloc(kv.m_value); + } + m_defs.reset(); + // m_case_defs does not own its data, no need to deallocate + m_case_defs.reset(); + m_util = 0; // force deletion + } + + util & plugin::u() const { + SASSERT(m_manager); + SASSERT(m_family_id != null_family_id); + if (m_util.get() == 0) { + m_util = alloc(util, *m_manager, m_family_id); + } + return *(m_util.get()); + } + + promise_def plugin::mk_def(symbol const& name, unsigned n, sort *const * params, sort * range) { + SASSERT(! m_defs.contains(name)); + def* d = u().decl_fun(name, n, params, range); + m_defs.insert(name, d); + return promise_def(&u(), d); + } + + void plugin::set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs) { + u().set_definition(d, n_vars, vars, rhs); + for (case_def & c : d.get_def()->get_cases()) { + m_case_defs.insert(c.get_name(), &c); + } + } + + def* plugin::mk_def(symbol const& name, unsigned n, sort ** params, sort * range, + unsigned n_vars, var ** vars, expr * rhs) { + SASSERT(! m_defs.contains(name)); + promise_def d = mk_def(name, n, params, range); + set_definition(d, n_vars, vars, rhs); + return d.get_def(); + } + + func_decl * plugin::mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) + { + VALIDATE_PARAM(m(), m().is_bool(range) && num_parameters == 1 && parameters[0].is_ast()); + func_decl_info info(m_family_id, OP_FUN_CASE_PRED, num_parameters, parameters); + info.m_private_parameters = true; + return m().mk_func_decl(symbol(parameters[0].get_symbol()), arity, domain, range, info); + } + + func_decl * plugin::mk_fun_defined_decl(decl_kind k, unsigned num_parameters, + parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) + { + VALIDATE_PARAM(m(), num_parameters == 1 && parameters[0].is_ast()); + func_decl_info info(m_family_id, k, num_parameters, parameters); + info.m_private_parameters = true; + return m().mk_func_decl(symbol(parameters[0].get_symbol()), arity, + domain, range, info); + } + + // generic declaration of symbols + func_decl * plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) + { + switch(k) { + case OP_FUN_CASE_PRED: + return mk_fun_pred_decl(num_parameters, parameters, arity, domain, range); + case OP_FUN_DEFINED: + return mk_fun_defined_decl(k, num_parameters, parameters, arity, domain, range); + default: + UNREACHABLE(); return 0; + } + } + } +} \ No newline at end of file diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h new file mode 100644 index 000000000..e9b75a409 --- /dev/null +++ b/src/ast/recfun_decl_plugin.h @@ -0,0 +1,246 @@ +/*++ +Module Name: + + recfun_decl_plugin.h + +Abstract: + + Declaration and definition of (potentially recursive) functions + +Author: + + Simon Cruanes 2017-11 + +Revision History: + +--*/ + +#pragma once + +#include "ast/ast.h" +#include "ast/rewriter/th_rewriter.h" + +namespace recfun { + class case_def; // cases; + + ast_manager & m_manager; + symbol m_name; // def_map; + typedef map case_def_map; + + mutable scoped_ptr m_util; + def_map m_defs; // function->def + case_def_map m_case_defs; // case_pred->def + svector m_def_block; + + ast_manager & m() { return *m_manager; } + public: + plugin(); + virtual ~plugin() override; + virtual void finalize() override; + + util & u() const; // build or return util + + virtual bool is_fully_interp(sort * s) const override { return false; } // might depend on unin sorts + + virtual decl_plugin * mk_fresh() override { return alloc(plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override { UNREACHABLE(); return 0; } + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) override; + + promise_def mk_def(symbol const& name, unsigned n, sort *const * params, sort * range); + + void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); + + def* mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs); + + bool has_def(const symbol& s) const { return m_defs.contains(s); } + def const& get_def(const symbol& s) const { return *(m_defs[s]); } + promise_def get_promise_def(const symbol &s) const { return promise_def(&u(), m_defs[s]); } + def& get_def(symbol const& s) { return *(m_defs[s]); } + bool has_case_def(const symbol& s) const { return m_case_defs.contains(s); } + case_def& get_case_def(symbol const& s) { SASSERT(has_case_def(s)); return *(m_case_defs[s]); } + bool is_declared(symbol const& s) const { return m_defs.contains(s); } + private: + func_decl * mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_fun_defined_decl(decl_kind k, + unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + }; + } + + // Varus utils for recursive functions + class util { + friend class decl::plugin; + + ast_manager & m_manager; + family_id m_family_id; + th_rewriter m_th_rw; + decl::plugin * m_plugin; + + bool compute_is_immediate(expr * rhs); + void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); + public: + util(ast_manager &m, family_id); + + ast_manager & m() { return m_manager; } + th_rewriter & get_th_rewriter() { return m_th_rw; } + bool is_case_pred(app * e) const { return is_app_of(e, m_family_id, OP_FUN_CASE_PRED); } + bool is_defined(app * e) const { return is_app_of(e, m_family_id, OP_FUN_DEFINED); } + bool owns_app(app * e) const { return e->get_family_id() == m_family_id; } + + //has_def(s)); + return m_plugin->get_def(s); + } + + case_def& get_case_def(symbol const & s) { + SASSERT(m_plugin->has_case_def(s)); + return m_plugin->get_case_def(s); + } + + app* mk_fun_defined(def const & d, unsigned n_args, expr * const * args) { + return m().mk_app(d.get_decl(), n_args, args); + } + app* mk_fun_defined(def const & d, ptr_vector const & args) { + return mk_fun_defined(d, args.size(), args.c_ptr()); + } + app* mk_case_pred(case_pred const & p, ptr_vector const & args) { + return m().mk_app(p.get_decl(), args.size(), args.c_ptr()); + } + }; +} + +typedef recfun::def recfun_def; +typedef recfun::case_def recfun_case_def; +typedef recfun::decl::plugin recfun_decl_plugin; +typedef recfun::util recfun_util; diff --git a/src/ast/reg_decl_plugins.cpp b/src/ast/reg_decl_plugins.cpp index 985ecee9e..f8abe81d8 100644 --- a/src/ast/reg_decl_plugins.cpp +++ b/src/ast/reg_decl_plugins.cpp @@ -22,6 +22,7 @@ Revision History: #include "ast/array_decl_plugin.h" #include "ast/bv_decl_plugin.h" #include "ast/datatype_decl_plugin.h" +#include "ast/recfun_decl_plugin.h" #include "ast/dl_decl_plugin.h" #include "ast/seq_decl_plugin.h" #include "ast/pb_decl_plugin.h" @@ -40,6 +41,9 @@ void reg_decl_plugins(ast_manager & m) { if (!m.get_plugin(m.mk_family_id(symbol("datatype")))) { m.register_plugin(symbol("datatype"), alloc(datatype_decl_plugin)); } + if (!m.get_plugin(m.mk_family_id(symbol("recfun")))) { + m.register_plugin(symbol("recfun"), alloc(recfun_decl_plugin)); + } if (!m.get_plugin(m.mk_family_id(symbol("datalog_relation")))) { m.register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin)); } diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index ec2eea032..75ed4e7eb 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -678,6 +678,8 @@ bool cmd_context::logic_has_datatype() const { return !has_logic() || smt_logics::logic_has_datatype(m_logic); } +bool cmd_context::logic_has_recfun() const { return true; } + void cmd_context::init_manager_core(bool new_manager) { SASSERT(m_manager != 0); SASSERT(m_pmanager != 0); @@ -690,6 +692,7 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("bv"), alloc(bv_decl_plugin), logic_has_bv()); register_plugin(symbol("array"), alloc(array_decl_plugin), logic_has_array()); register_plugin(symbol("datatype"), alloc(datatype_decl_plugin), logic_has_datatype()); + register_plugin(symbol("recfun"), alloc(recfun_decl_plugin), logic_has_recfun()); register_plugin(symbol("seq"), alloc(seq_decl_plugin), logic_has_seq()); register_plugin(symbol("pb"), alloc(pb_decl_plugin), logic_has_pb()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); @@ -705,6 +708,7 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("bv"), logic_has_bv(), fids); load_plugin(symbol("array"), logic_has_array(), fids); load_plugin(symbol("datatype"), logic_has_datatype(), fids); + load_plugin(symbol("recfun"), logic_has_recfun(), fids); load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); load_plugin(symbol("pb"), logic_has_pb(), fids); @@ -868,7 +872,24 @@ void cmd_context::insert(symbol const & s, object_ref * r) { m_object_refs.insert(s, r); } -void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, svector const& ids, expr* e) { +recfun_decl_plugin * cmd_context::get_recfun_plugin() { + ast_manager & m = get_ast_manager(); + family_id id = m.get_family_id("recfun"); + recfun_decl_plugin* p = reinterpret_cast(m.get_plugin(id)); + SASSERT(p); + return p; +} + + +recfun::promise_def cmd_context::decl_rec_fun(const symbol &name, unsigned int arity, sort *const *domain, sort *range) { + SASSERT(logic_has_recfun()); + recfun_decl_plugin* p = get_recfun_plugin(); + recfun::promise_def def = p->mk_def(name, arity, domain, range); + return def; +} + +// insert a recursive function as a regular quantified axiom +void cmd_context::insert_rec_fun_as_axiom(func_decl *f, expr_ref_vector const& binding, svector const &ids, expr* e) { expr_ref eq(m()); app_ref lhs(m()); lhs = m().mk_app(f, binding.size(), binding.c_ptr()); @@ -899,6 +920,31 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s assert_expr(eq); } +// TODO: make this a parameter +#define USE_NATIVE_RECFUN 1 + +void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, svector const& ids, expr* rhs) { + TRACE("recfun", tout<<"define recfun " << f->get_name() + <<" = " << mk_pp(rhs, m()) << "\n";); + + if (! USE_NATIVE_RECFUN) { + // just use an axiom + insert_rec_fun_as_axiom(f, binding, ids, rhs); + return; + } + + recfun_decl_plugin* p = get_recfun_plugin(); + + var_ref_vector vars(m()); + for (expr* b : binding) { + SASSERT(is_var(b)); + vars.push_back(to_var(b)); + } + + recfun::promise_def d = p->get_promise_def(f->get_name()); + p->set_definition(d, vars.size(), vars.c_ptr(), rhs); +} + func_decl * cmd_context::find_func_decl(symbol const & s) const { builtin_decl d; if (m_builtin_decls.find(s, d)) { diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index 99d6c8284..b41eab42d 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -29,6 +29,7 @@ Notes: #include "util/dictionary.h" #include "solver/solver.h" #include "ast/datatype_decl_plugin.h" +#include "ast/recfun_decl_plugin.h" #include "util/stopwatch.h" #include "util/cmd_context_types.h" #include "util/event_handler.h" @@ -289,6 +290,7 @@ protected: bool logic_has_array() const; bool logic_has_datatype() const; bool logic_has_fpa() const; + bool logic_has_recfun() const; void print_unsupported_msg() { regular_stream() << "unsupported" << std::endl; } void print_unsupported_info(symbol const& s, int line, int pos) { if (s != symbol::null) diagnostic_stream() << "; " << s << " line: " << line << " position: " << pos << std::endl;} @@ -304,6 +306,7 @@ protected: void erase_macro(symbol const& s); bool macros_find(symbol const& s, unsigned n, expr*const* args, expr*& t) const; + recfun_decl_plugin * get_recfun_plugin(); public: cmd_context(bool main_ctx = true, ast_manager * m = 0, symbol const & l = symbol::null); @@ -382,9 +385,11 @@ public: void insert_user_tactic(symbol const & s, sexpr * d); void insert_aux_pdecl(pdecl * p); void insert_rec_fun(func_decl* f, expr_ref_vector const& binding, svector const& ids, expr* e); + void insert_rec_fun_as_axiom(func_decl* f, expr_ref_vector const& binding, svector const& ids, expr* e); func_decl * find_func_decl(symbol const & s) const; func_decl * find_func_decl(symbol const & s, unsigned num_indices, unsigned const * indices, unsigned arity, sort * const * domain, sort * range) const; + recfun::promise_def decl_rec_fun(const symbol &name, unsigned int arity, sort *const *domain, sort *range); psort_decl * find_psort_decl(symbol const & s) const; cmd * find_cmd(symbol const & s) const; sexpr * find_user_tactic(symbol const & s) const; diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index a06438c73..58cc80b08 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -2278,7 +2278,7 @@ namespace smt2 { next(); } - void parse_rec_fun_decl(func_decl_ref& f, expr_ref_vector& bindings, svector& ids) { + recfun::promise_def parse_rec_fun_decl(func_decl_ref& f, expr_ref_vector& bindings, svector& ids) { SASSERT(m_num_bindings == 0); check_identifier("invalid function/constant definition, symbol expected"); symbol id = curr_id(); @@ -2289,7 +2289,8 @@ namespace smt2 { unsigned num_vars = parse_sorted_vars(); SASSERT(num_vars == m_num_bindings); parse_sort("Invalid recursive function definition"); - f = m().mk_func_decl(id, num_vars, sort_stack().c_ptr() + sort_spos, sort_stack().back()); + recfun::promise_def pdef = m_ctx.decl_rec_fun(id, num_vars, sort_stack().c_ptr() + sort_spos, sort_stack().back()); + f = pdef.get_def()->get_decl(); bindings.append(num_vars, expr_stack().c_ptr() + expr_spos); ids.append(num_vars, symbol_stack().c_ptr() + sym_spos); symbol_stack().shrink(sym_spos); @@ -2297,6 +2298,7 @@ namespace smt2 { expr_stack().shrink(expr_spos); m_env.end_scope(); m_num_bindings = 0; + return pdef; } void parse_rec_fun_bodies(func_decl_ref_vector const& decls, vector const& bindings, vector >const & ids) { diff --git a/src/smt/CMakeLists.txt b/src/smt/CMakeLists.txt index e102bd28b..0d98472b7 100644 --- a/src/smt/CMakeLists.txt +++ b/src/smt/CMakeLists.txt @@ -58,6 +58,7 @@ z3_add_component(smt theory_lra.cpp theory_opt.cpp theory_pb.cpp + theory_recfun.cpp theory_seq.cpp theory_str.cpp theory_utvpi.cpp diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index a8eb81a2e..ec91ec608 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -27,6 +27,7 @@ void smt_params::updt_local_params(params_ref const & _p) { m_random_seed = p.random_seed(); m_relevancy_lvl = p.relevancy(); m_ematching = p.ematching(); + m_recfun_max_depth = p.recfun_max_depth(); m_phase_selection = static_cast(p.phase_selection()); m_restart_strategy = static_cast(p.restart_strategy()); m_restart_factor = p.restart_factor(); diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index b01499c04..e46b89401 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -105,6 +105,9 @@ struct smt_params : public preprocessor_params, bool m_new_core2th_eq; bool m_ematching; + // TODO: move into its own file? + unsigned m_recfun_max_depth; + // ----------------------------------- // // Case split strategy @@ -258,6 +261,7 @@ struct smt_params : public preprocessor_params, m_display_features(false), m_new_core2th_eq(true), m_ematching(true), + m_recfun_max_depth(500), m_case_split_strategy(CS_ACTIVITY_DELAY_NEW), m_rel_case_split_order(0), m_lookahead_diseq(false), diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 937aa6a2b..96923ec5e 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -83,5 +83,6 @@ def_module_params(module_name='smt', ('core.extend_patterns', BOOL, False, 'extend unsat core with literals that trigger (potential) quantifier instances'), ('core.extend_patterns.max_distance', UINT, UINT_MAX, 'limits the distance of a pattern-extended unsat core'), ('core.extend_nonlocal_patterns', BOOL, False, 'extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier\'s body'), - ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none') + ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), + ('recfun.max_depth', UINT, 500, 'maximum depth of unrolling for recursive functions') )) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index bbc91e4c6..e8d55e0d4 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -28,6 +28,7 @@ Revision History: #include "smt/theory_array_full.h" #include "smt/theory_bv.h" #include "smt/theory_datatype.h" +#include "smt/theory_recfun.h" #include "smt/theory_dummy.h" #include "smt/theory_dl.h" #include "smt/theory_seq_empty.h" @@ -217,6 +218,7 @@ namespace smt { void setup::setup_QF_DT() { setup_QF_UF(); setup_datatypes(); + setup_recfuns(); } void setup::setup_QF_BVRE() { @@ -845,6 +847,13 @@ namespace smt { m_context.register_plugin(alloc(theory_datatype, m_manager, m_params)); } + void setup::setup_recfuns() { + TRACE("recfun", tout << "registering theory recfun...\n";); + theory_recfun * th = alloc(theory_recfun, m_manager); + m_context.register_plugin(th); + th->setup_params(); + } + void setup::setup_dl() { m_context.register_plugin(mk_theory_dl(m_manager)); } @@ -898,6 +907,7 @@ namespace smt { setup_arrays(); setup_bv(); setup_datatypes(); + setup_recfuns(); setup_dl(); setup_seq_str(st); setup_card(); diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 924c2caec..ce8f44047 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -93,6 +93,7 @@ namespace smt { void setup_unknown(static_features & st); void setup_arrays(); void setup_datatypes(); + void setup_recfuns(); void setup_bv(); void setup_arith(); void setup_dl(); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp new file mode 100644 index 000000000..db9f7d938 --- /dev/null +++ b/src/smt/theory_recfun.cpp @@ -0,0 +1,363 @@ + +#include "util/stats.h" +#include "ast/ast_util.h" +#include "smt/theory_recfun.h" +#include "smt/params/smt_params_helper.hpp" + +#define DEBUG(x) \ + do { \ + TRACE("recfun", tout << x << '\n';); \ + auto& out = std::cout; out << "recfun: "; out << x; out << '\n' << std::flush; } while(0) + +// NOTE: debug +struct pp_lits { + smt::context & ctx; + smt::literal *lits; + unsigned len; + pp_lits(smt::context & ctx, unsigned len, smt::literal *lits) : ctx(ctx), lits(lits), len(len) {} +}; + +std::ostream & operator<<(std::ostream & out, pp_lits const & pp) { + out << "clause{"; + bool first = true; + for (auto i = 0; i < pp.len; ++i) { + if (first) { first = false; } else { out << " ∨ "; } + pp.ctx.display_detailed_literal(out, pp.lits[i]); + } + return out << "}"; +} + +namespace smt { + + theory_recfun::theory_recfun(ast_manager & m) + : theory(m.mk_family_id("recfun")), m_util(0), m_trail(*this), + m_guards(), m_max_depth(0), m_q_case_expand(), m_q_body_expand() + { + recfun_decl_plugin * plugin = + reinterpret_cast(m.get_plugin(get_family_id())); + SASSERT(plugin); + m_util = & plugin->u(); + SASSERT(m_util); + } + + theory_recfun::~theory_recfun() { + reset_queues(); + for (auto & kv : m_guards) { + m().dec_ref(kv.m_key); + } + m_guards.reset(); + } + + char const * theory_recfun::get_name() const { return "recfun"; } + + void theory_recfun::setup_params() { + // obtain max depth via parameters + smt_params_helper p(get_context().get_params()); + set_max_depth(p.recfun_max_depth()); + } + + theory* theory_recfun::mk_fresh(context* new_ctx) { + return alloc(theory_recfun, new_ctx->get_manager()); + } + + bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { + context & ctx = get_context(); + if (! ctx.e_internalized(atom)) { + unsigned num_args = atom->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) + ctx.internalize(atom->get_arg(i), false); + ctx.mk_enode(atom, false, true, false); + } + if (! ctx.b_internalized(atom)) { + bool_var v = ctx.mk_bool_var(atom); + ctx.set_var_theory(v, get_id()); + } + return true; + } + + bool theory_recfun::internalize_term(app * term) { + DEBUG("internalizing term: " << mk_pp(term, m())); + context & ctx = get_context(); + for (expr* e : *term) ctx.internalize(e, false); + // the internalization of the arguments may have triggered the internalization of term. + if (ctx.e_internalized(term)) + return true; + ctx.mk_enode(term, false, false, true); + return true; // the theory doesn't actually map terms to variables + } + + void theory_recfun::reset_queues() { + m_q_case_expand.reset(); + m_q_body_expand.reset(); + } + + void theory_recfun::reset_eh() { + m_trail.reset(); + reset_queues(); + } + + /* + * when `n` becomes relevant, if it's `f(t1…tn)` with `f` defined, + * then case-expand `n`. If it's a macro we can also immediately + * body-expand it. + */ + void theory_recfun::relevant_eh(app * n) { + SASSERT(get_context().relevancy()); + if (u().is_defined(n)) { + DEBUG("relevant_eh: (defined) " << mk_pp(n, m())); + + case_expansion e(u(), n); + push_case_expand(std::move(e)); + } + } + + void theory_recfun::push_scope_eh() { + theory::push_scope_eh(); + m_trail.push_scope(); + } + + void theory_recfun::pop_scope_eh(unsigned num_scopes) { + m_trail.pop_scope(num_scopes); + theory::pop_scope_eh(num_scopes); + reset_queues(); + } + + void theory_recfun::restart_eh() { + m_trail.reset(); + reset_queues(); + } + + bool theory_recfun::can_propagate() { + return ! (m_q_case_expand.empty() && m_q_body_expand.empty()); + } + + void theory_recfun::propagate() { + for (case_expansion & e : m_q_case_expand) { + if (e.m_def->is_fun_macro()) { + // body expand immediately + assert_macro_axiom(e); + } + else { + // case expand + SASSERT(e.m_def->is_fun_defined()); + assert_case_axioms(e); + } + } + m_q_case_expand.clear(); + + for (body_expansion & e : m_q_body_expand) { + assert_body_axiom(e); + } + m_q_body_expand.clear(); + } + + void theory_recfun::max_depth_conflict() { + DEBUG("max-depth conflict"); + // TODO: build clause from `m_guards` + /* + context & ctx = get_context(); + region & r = ctx.get_region(); + ctx.set_conflict( + */ + } + + // if `is_true` and `v = C_f_i(t1…tn)`, then body-expand i-th case of `f(t1…tn)` + void theory_recfun::assign_eh(bool_var v, bool is_true) { + expr* e = get_context().bool_var2expr(v); + DEBUG("assign_eh "<< mk_pp(e,m())); + if (!is_true) return; + if (!is_app(e)) return; + app* a = to_app(e); + if (u().is_case_pred(a)) { + DEBUG("assign_case_pred_true "<< mk_pp(e,m())); + // add to set of local assumptions, for depth-limit purpose + { + m_guards.insert(e, empty()); + m().inc_ref(e); + insert_ref_map trail_elt(m(), m_guards, e); + m_trail.push(trail_elt); + } + if (m_guards.size() > get_max_depth()) { + // too many body-expansions: depth-limit conflict + max_depth_conflict(); + } + else { + // body-expand + body_expansion b_e(u(), a); + push_body_expand(std::move(b_e)); + } + } + } + + // replace `vars` by `args` in `e` + expr_ref theory_recfun::apply_args(recfun::vars const & vars, + ptr_vector const & args, + expr * e) { + // check that var order is standard + SASSERT(vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0); + var_subst subst(m(), true); + expr_ref new_body(m()); + subst(e, args.size(), args.c_ptr(), new_body); + get_context().get_rewriter()(new_body); // simplify + return new_body; + } + + app_ref theory_recfun::apply_pred(recfun::case_pred const & p, + ptr_vector const & args){ + app_ref res(u().mk_case_pred(p, args), m()); + return res; + } + + void theory_recfun::assert_macro_axiom(case_expansion & e) { + DEBUG("assert_macro_axiom " << pp_case_expansion(e,m())); + SASSERT(e.m_def->is_fun_macro()); + expr * lhs = e.m_lhs; + context & ctx = get_context(); + auto & vars = e.m_def->get_vars(); + // substitute `e.args` into the macro RHS + expr * rhs = apply_args(vars, e.m_args, e.m_def->get_macro_rhs()); + DEBUG("macro expansion yields" << mk_pp(rhs,m())); + // now build the axiom `lhs = rhs` + ctx.internalize(rhs, false); + DEBUG("adding axiom: " << mk_pp(lhs, m()) << " = " << mk_pp(rhs, m())); + if (m().proofs_enabled()) { + // add unit clause `lhs=rhs` + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + literal lits[1] = {l}; + ctx.mk_th_axiom(get_id(), 1, lits); + } + else { + //region r; + enode * e_lhs = ctx.get_enode(lhs); + enode * e_rhs = ctx.get_enode(rhs); + // TODO: proper justification + //justification * js = ctx.mk_justification( + ctx.assign_eq(e_lhs, e_rhs, eq_justification()); + } + } + + void theory_recfun::assert_case_axioms(case_expansion & e) { + DEBUG("assert_case_axioms "<< pp_case_expansion(e,m()) + << " with " << e.m_def->get_cases().size() << " cases"); + SASSERT(e.m_def->is_fun_defined()); + context & ctx = get_context(); + // add case-axioms for all case-paths + auto & vars = e.m_def->get_vars(); + for (recfun::case_def const & c : e.m_def->get_cases()) { + // applied predicate to `args` + app_ref pred_applied = apply_pred(c.get_pred(), e.m_args); + SASSERT(u().owns_app(pred_applied)); + // substitute arguments in `path` + expr_ref_vector path(m()); + for (auto & g : c.get_guards()) { + expr_ref g_applied = apply_args(vars, e.m_args, g); + path.push_back(g_applied); + } + // assert `p(args) <=> And(guards)` (with CNF on the fly) + ctx.internalize(pred_applied, false); + // FIXME: we need to be informed wen `pred_applied` is true!! + ctx.mark_as_relevant(ctx.get_bool_var(pred_applied)); + literal concl = ctx.get_literal(pred_applied); + { + // assert `guards=>p(args)` + literal_vector c; + c.push_back(concl); + for (expr* g : path) { + ctx.internalize(g, false); + c.push_back(~ ctx.get_literal(g)); + } + + //TRACE("recfun", tout << "assert_case_axioms " << pp_case_expansion(e) + // << " axiom " << mk_pp(*l) <<"\n";); + DEBUG("assert_case_axiom " << pp_lits(get_context(), path.size()+1, c.c_ptr())); + get_context().mk_th_axiom(get_id(), path.size()+1, c.c_ptr()); + } + { + // assert `p(args) => guards[i]` for each `i` + for (expr * _g : path) { + SASSERT(ctx.b_internalized(_g)); + literal g = ctx.get_literal(_g); + literal c[2] = {~ concl, g}; + + DEBUG("assert_case_axiom " << pp_lits(get_context(), 2, c)); + get_context().mk_th_axiom(get_id(), 2, c); + } + } + + // also body-expand paths that do not depend on any defined fun + if (c.is_immediate()) { + body_expansion be(c, e.m_args); + assert_body_axiom(be); + } + } + } + + void theory_recfun::assert_body_axiom(body_expansion & e) { + DEBUG("assert_body_axioms "<< pp_body_expansion(e,m())); + context & ctx = get_context(); + recfun::def & d = *e.m_cdef->get_def(); + auto & vars = d.get_vars(); + auto & args = e.m_args; + // check that var order is standard + SASSERT(vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0); + expr_ref lhs(u().mk_fun_defined(d, args), m()); + // substitute `e.args` into the RHS of this particular case + expr_ref rhs = apply_args(vars, args, e.m_cdef->get_rhs()); + // substitute `e.args` into the guard of this particular case, to make + // the `condition` part of the clause `conds => lhs=rhs` + expr_ref_vector guards(m()); + for (auto & g : e.m_cdef->get_guards()) { + expr_ref new_guard = apply_args(vars, args, g); + guards.push_back(new_guard); + } + // now build the axiom `conds => lhs = rhs` + ctx.internalize(rhs, false); + for (auto& g : guards) ctx.internalize(g, false); + + // add unit clause `conds => lhs=rhs` + literal_vector clause; + for (auto& g : guards) { + ctx.internalize(g, false); + literal l = ~ ctx.get_literal(g); + ctx.mark_as_relevant(l); + clause.push_back(l); + } + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + clause.push_back(l); + DEBUG("assert_body_axiom " << pp_lits(get_context(), clause.size(), clause.c_ptr())); + ctx.mk_th_axiom(get_id(), clause.size(), clause.c_ptr()); + } + + final_check_status theory_recfun::final_check_eh() { + return FC_DONE; + } + + void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { + DEBUG("add_theory_assumptions"); + // TODO: add depth-limit assumptions? + } + + void theory_recfun::display(std::ostream & out) const { + out << "recfun{}"; + } + + void theory_recfun::collect_statistics(::statistics & st) const { + st.update("recfun macro expansion", m_stats.m_macro_expansions); + st.update("recfun case expansion", m_stats.m_case_expansions); + st.update("recfun body expansion", m_stats.m_body_expansions); + } + + std::ostream& operator<<(std::ostream & out, theory_recfun::pp_case_expansion const & e) { + return out << "case_exp(" << mk_pp(e.e.m_lhs, e.m) << ")"; + } + + std::ostream& operator<<(std::ostream & out, theory_recfun::pp_body_expansion const & e) { + out << "body_exp(" << e.e.m_cdef->get_name(); + for (auto* t : e.e.m_args) { + out << " " << mk_pp(t,e.m); + } + return out << ")"; + } +} diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h new file mode 100644 index 000000000..5645a1893 --- /dev/null +++ b/src/smt/theory_recfun.h @@ -0,0 +1,155 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_recfun.h + +Abstract: + + Theory responsible for unrolling recursive functions + +Author: + + Leonardo de Moura (leonardo) 2008-10-31. + +Revision History: + +--*/ +#ifndef THEORY_RECFUN_H_ +#define THEORY_RECFUN_H_ + +#include "smt/smt_theory.h" +#include "smt/smt_context.h" +#include "ast/ast_pp.h" +#include "ast/recfun_decl_plugin.h" + +namespace smt { + + class theory_recfun : public theory { + struct stats { + unsigned m_case_expansions, m_body_expansions, m_macro_expansions; + void reset() { memset(this, 0, sizeof(stats)); } + stats() { reset(); } + }; + + // one case-expansion of `f(t1…tn)` + struct case_expansion { + expr * m_lhs; // the term to expand + recfun_def * m_def; + ptr_vector m_args; + case_expansion(recfun_util& u, app * n) : m_lhs(n), m_def(0), m_args() + { + SASSERT(u.is_defined(n)); + func_decl * d = n->get_decl(); + const symbol& name = d->get_name(); + m_def = &u.get_def(name); + m_args.append(n->get_num_args(), n->get_args()); + } + case_expansion(case_expansion const & from) + : m_lhs(from.m_lhs), + m_def(from.m_def), + m_args(from.m_args) {} + case_expansion(case_expansion && from) + : m_lhs(from.m_lhs), + m_def(from.m_def), + m_args(std::move(from.m_args)) {} + }; + + struct pp_case_expansion { + case_expansion & e; + ast_manager & m; + pp_case_expansion(case_expansion & e, ast_manager & m) : e(e), m(m) {} + }; + + friend std::ostream& operator<<(std::ostream&, pp_case_expansion const &); + + // one body-expansion of `f(t1…tn)` using a `C_f_i(t1…tn)` + struct body_expansion { + recfun_case_def const * m_cdef; + ptr_vector m_args; + + body_expansion(recfun_util& u, app * n) : m_cdef(0), m_args() { + SASSERT(u.is_case_pred(n)); + func_decl * d = n->get_decl(); + const symbol& name = d->get_name(); + m_cdef = &u.get_case_def(name); + for (unsigned i = 0; i < n->get_num_args(); ++i) + m_args.push_back(n->get_arg(i)); + } + body_expansion(recfun_case_def const & d, ptr_vector & args) : m_cdef(&d), m_args(args) {} + body_expansion(body_expansion const & from): m_cdef(from.m_cdef), m_args(from.m_args) {} + body_expansion(body_expansion && from) : m_cdef(from.m_cdef), m_args(std::move(from.m_args)) {} + }; + + struct pp_body_expansion { + body_expansion & e; + ast_manager & m; + pp_body_expansion(body_expansion & e, ast_manager & m) : e(e), m(m) {} + }; + + friend std::ostream& operator<<(std::ostream&, pp_body_expansion const &); + + struct empty{}; + + typedef trail_stack th_trail_stack; + typedef obj_map guard_set; + + recfun_util * m_util; + stats m_stats; + th_trail_stack m_trail; + guard_set m_guards; // true case-preds + unsigned m_max_depth; // for fairness and termination + + vector m_q_case_expand; + vector m_q_body_expand; + + recfun_util & u() const { SASSERT(m_util); return *m_util; } + ast_manager & m() { return get_manager(); } + bool is_defined(app * f) const { return u().is_defined(f); } + bool is_case_pred(app * f) const { return u().is_case_pred(f); } + + bool is_defined(enode * e) const { return is_defined(e->get_owner()); } + bool is_case_pred(enode * e) const { return is_case_pred(e->get_owner()); } + + void reset_queues(); + expr_ref apply_args(recfun::vars const & vars, ptr_vector const & args, expr * e); //!< substitute variables by args + app_ref apply_pred(recfun::case_pred const & p, ptr_vector const & args); //0); m_max_depth = n; } + }; +} + +#endif diff --git a/src/util/scoped_ptr_vector.h b/src/util/scoped_ptr_vector.h index 0bd0fd47e..15b9b889c 100644 --- a/src/util/scoped_ptr_vector.h +++ b/src/util/scoped_ptr_vector.h @@ -31,6 +31,7 @@ public: void reset() { std::for_each(m_vector.begin(), m_vector.end(), delete_proc()); m_vector.reset(); } void push_back(T * ptr) { m_vector.push_back(ptr); } void pop_back() { SASSERT(!empty()); set(size()-1, 0); m_vector.pop_back(); } + T * back() const { return m_vector.back(); } T * operator[](unsigned idx) const { return m_vector[idx]; } void set(unsigned idx, T * ptr) { if (m_vector[idx] == ptr) @@ -51,6 +52,13 @@ public: push_back(0); } } + //!< swap last element with given pointer + void swap_back(scoped_ptr & ptr) { + SASSERT(!empty()); + T * tmp = ptr.detach(); + ptr = m_vector.back(); + m_vector[m_vector.size()-1] = tmp; + } }; #endif diff --git a/src/util/trail.h b/src/util/trail.h index bba71fb00..0503bcfa7 100644 --- a/src/util/trail.h +++ b/src/util/trail.h @@ -143,6 +143,17 @@ public: }; +template +class insert_ref_map : public trail { + Mgr& m; + M& m_map; + D m_obj; +public: + insert_ref_map(Mgr& m, M& t, D o) : m(m), m_map(t), m_obj(o) {} + virtual ~insert_ref_map() {} + virtual void undo(Ctx & ctx) { m_map.remove(m_obj); m.dec_ref(m_obj); } +}; + template class push_back_vector : public trail { From 06e0b12700b85f90dfa344958642bc1e0fd2ab9b Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 6 Dec 2017 13:01:54 +0100 Subject: [PATCH 03/65] add a predicate for depth limit assumptions --- src/ast/recfun_decl_plugin.cpp | 29 +++++++++++++++++++++++++- src/ast/recfun_decl_plugin.h | 38 ++++++++++++++++++++++++++++++++++ src/smt/params/smt_params.h | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 331114910..b8bf1637a 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -342,7 +342,7 @@ namespace recfun { */ util::util(ast_manager & m, family_id id) - : m_manager(m), m_family_id(id), m_th_rw(m), m_plugin(0) { + : m_manager(m), m_family_id(id), m_th_rw(m), m_plugin(0), m_dlimit_map() { m_plugin = dynamic_cast(m.get_plugin(m_family_id)); } @@ -354,6 +354,15 @@ namespace recfun { d.set_definition(n_vars, vars, rhs); } + // get or create predicate for depth limit + depth_limit_pred_ref util::get_depth_limit_pred(unsigned d) { + depth_limit_pred * pred = nullptr; + if (! m_dlimit_map.find(d, pred)) { + pred = alloc(depth_limit_pred, m(), m_family_id, d); + m_dlimit_map.insert(d, pred); + } + return depth_limit_pred_ref(pred, *this); + } // used to know which `app` are from this theory struct is_imm_pred : is_immediate_pred { @@ -387,6 +396,17 @@ namespace recfun { d->compute_cases(is_i, u->get_th_rewriter(), n_vars, vars, rhs); } + depth_limit_pred::depth_limit_pred(ast_manager & m, family_id fid, unsigned d) + : m_name_buf(), m_name(""), m_depth(d), m_decl(m) { + // build name, then build decl + std::ostringstream tmpname(m_name_buf); + tmpname << "depth_limit_" << d; + m_name = symbol(m_name_buf.c_str()); + parameter params[1] = { parameter(d) }; + func_decl_info info(fid, OP_DEPTH_LIMIT, 1, params); + m_decl = m.mk_func_decl(m_name, 0, ((sort*const *)nullptr), m.mk_bool_sort(), info); + } + namespace decl { plugin::plugin() : decl_plugin(), m_defs(), m_case_defs(), m_def_block() {} plugin::~plugin() { finalize(); } @@ -424,6 +444,13 @@ namespace recfun { } } + app_ref plugin::mk_depth_limit_pred(unsigned d) { + depth_limit_pred_ref p = u().get_depth_limit_pred(d); + app_ref res(m()); + m().mk_app(p->get_decl(), 0, nullptr, res); + return res; + } + def* plugin::mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs) { SASSERT(! m_defs.contains(name)); diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index e9b75a409..677a44302 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -30,6 +30,7 @@ namespace recfun { enum op_kind { OP_FUN_DEFINED, // defined function with one or more cases, possibly recursive OP_FUN_CASE_PRED, // predicate guarding a given control flow path + OP_DEPTH_LIMIT, // predicate enforcing some depth limit }; /*! A predicate `p(t1…tn)`, that, if true, means `f(t1…tn)` is following @@ -145,6 +146,27 @@ namespace recfun { def * get_def() const { return d; } }; + // predicate for limiting unrolling depth, to be used in assumptions and conflicts + class depth_limit_pred { + friend class util; + std::string m_name_buf; + symbol m_name; + unsigned m_depth; + func_decl_ref m_decl; + unsigned m_refcount; + + void inc_ref() { m_refcount ++; } + void dec_ref() { SASSERT(m_refcount > 0); m_refcount --; } + public: + depth_limit_pred(ast_manager & m, family_id fid, unsigned d); + unsigned get_depth() const { return m_depth; } + symbol const & get_name() const { return m_name; } + func_decl * get_decl() const { return m_decl.get(); } + }; + + // A reference to `depth_limit_pred` + typedef obj_ref depth_limit_pred_ref; + namespace decl { class plugin : public decl_plugin { @@ -186,6 +208,7 @@ namespace recfun { bool has_case_def(const symbol& s) const { return m_case_defs.contains(s); } case_def& get_case_def(symbol const& s) { SASSERT(has_case_def(s)); return *(m_case_defs[s]); } bool is_declared(symbol const& s) const { return m_defs.contains(s); } + app_ref mk_depth_limit_pred(unsigned d); private: func_decl * mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range); @@ -198,11 +221,14 @@ namespace recfun { // Varus utils for recursive functions class util { friend class decl::plugin; + + typedef map> depth_limit_map; ast_manager & m_manager; family_id m_family_id; th_rewriter m_th_rw; decl::plugin * m_plugin; + depth_limit_map m_dlimit_map; bool compute_is_immediate(expr * rhs); void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); @@ -237,10 +263,22 @@ namespace recfun { app* mk_case_pred(case_pred const & p, ptr_vector const & args) { return m().mk_app(p.get_decl(), args.size(), args.c_ptr()); } + + void inc_ref(depth_limit_pred * p) { p->inc_ref(); } + void dec_ref(depth_limit_pred * p) { + p->dec_ref(); + if (p->m_refcount == 0) { + m_dlimit_map.remove(p->m_depth); + dealloc(p); + } + } + + depth_limit_pred_ref get_depth_limit_pred(unsigned d); }; } typedef recfun::def recfun_def; typedef recfun::case_def recfun_case_def; +typedef recfun::depth_limit_pred recfun_depth_limit_pred; typedef recfun::decl::plugin recfun_decl_plugin; typedef recfun::util recfun_util; diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index e46b89401..330d284c4 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -261,7 +261,7 @@ struct smt_params : public preprocessor_params, m_display_features(false), m_new_core2th_eq(true), m_ematching(true), - m_recfun_max_depth(500), + m_recfun_max_depth(50), m_case_split_strategy(CS_ACTIVITY_DELAY_NEW), m_rel_case_split_order(0), m_lookahead_diseq(false), From 7b1e1d52e72dd06c2cf06a8604bb189c602eec14 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 6 Dec 2017 14:05:14 +0100 Subject: [PATCH 04/65] wip: conflicts for pruning branches with too many unrollings use the local assumption on depth to ensure the conflict clause is valid --- src/ast/recfun_decl_plugin.cpp | 38 +++++++--------- src/ast/recfun_decl_plugin.h | 2 +- src/smt/theory_recfun.cpp | 81 ++++++++++++++++++++++++++++------ 3 files changed, 85 insertions(+), 36 deletions(-) diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index b8bf1637a..3ebd888c7 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -23,10 +23,9 @@ Revision History: #include "ast/ast_pp.h" #include "util/scoped_ptr_vector.h" -#define DEBUG(x) do { auto& out = std::cout; out << "recfun: "; x; out << '\n' << std::flush; } while(0) +#define DEBUG(x) TRACE("recfun", tout << x << '\n';) #define VALIDATE_PARAM(m, _pred_) if (!(_pred_)) m.raise_exception("invalid parameter to recfun " #_pred_); - namespace recfun { case_pred::case_pred(ast_manager & m, family_id fid, std::string const & s, sort_ref_vector const & domain) : m_name(), m_name_buf(s), m_domain(domain), m_decl(m) @@ -186,10 +185,7 @@ namespace recfun { void def::add_case(std::string & name, unsigned n_conditions, expr ** conditions, expr * rhs, bool is_imm) { case_def c(m(), m_fid, this, name, get_domain(), n_conditions, conditions, rhs); c.set_is_immediate(is_imm); - TRACE("recfun", tout << "add_case " << name << " " << mk_pp(rhs, m()) - << " :is_imm " << is_imm - << " :guards " << mk_pp_vec(n_conditions, (ast**)conditions, m());); - DEBUG(out << "add_case " << name << " " << mk_pp(rhs, m()) + DEBUG("add_case " << name << " " << mk_pp(rhs, m()) << " :is_imm " << is_imm << " :guards " << mk_pp_vec(n_conditions, (ast**)conditions, m())); m_cases.push_back(c); @@ -201,12 +197,12 @@ namespace recfun { unsigned n_vars, var *const * vars, expr* rhs0) { if (m_cases.size() != 0) { - TRACE("recfun", tout << "bug: cases for " << m_name << " has cases already";); + DEBUG("bug: cases for " << m_name << " has cases already"); UNREACHABLE(); } SASSERT(n_vars = m_domain.size()); - DEBUG(out << "compute cases " << mk_pp(rhs0, m())); + DEBUG("compute cases " << mk_pp(rhs0, m())); unsigned case_idx = 0; std::string name; @@ -226,7 +222,7 @@ namespace recfun { th_rw(rhs0, simplified_rhs); rhs = simplified_rhs.get(); - DEBUG(out << "simplified into " << mk_pp(rhs, m())); + DEBUG("simplified into " << mk_pp(rhs, m())); #else expr* rhs = rhs0; #endif @@ -252,7 +248,7 @@ namespace recfun { } while (! st.empty()) { - DEBUG(out << "main loop iter"); + DEBUG("main loop iter"); branch b = st.pop_branch(); @@ -334,7 +330,7 @@ namespace recfun { } } - TRACE("recfun", tout << "done analysing " << get_name();); + DEBUG("done analysing " << get_name()); } /* @@ -364,6 +360,12 @@ namespace recfun { return depth_limit_pred_ref(pred, *this); } + app_ref util::mk_depth_limit_pred(unsigned d) { + depth_limit_pred_ref p = get_depth_limit_pred(d); + app_ref res(m().mk_const(p->get_decl()), m()); + return res; + } + // used to know which `app` are from this theory struct is_imm_pred : is_immediate_pred { util & u; @@ -399,12 +401,13 @@ namespace recfun { depth_limit_pred::depth_limit_pred(ast_manager & m, family_id fid, unsigned d) : m_name_buf(), m_name(""), m_depth(d), m_decl(m) { // build name, then build decl - std::ostringstream tmpname(m_name_buf); - tmpname << "depth_limit_" << d; + std::ostringstream tmpname; + tmpname << "depth_limit_" << d << std::flush; + m_name_buf.append(tmpname.str()); m_name = symbol(m_name_buf.c_str()); parameter params[1] = { parameter(d) }; func_decl_info info(fid, OP_DEPTH_LIMIT, 1, params); - m_decl = m.mk_func_decl(m_name, 0, ((sort*const *)nullptr), m.mk_bool_sort(), info); + m_decl = m.mk_const_decl(m_name, m.mk_bool_sort(), info); } namespace decl { @@ -444,13 +447,6 @@ namespace recfun { } } - app_ref plugin::mk_depth_limit_pred(unsigned d) { - depth_limit_pred_ref p = u().get_depth_limit_pred(d); - app_ref res(m()); - m().mk_app(p->get_decl(), 0, nullptr, res); - return res; - } - def* plugin::mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs) { SASSERT(! m_defs.contains(name)); diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 677a44302..272ab43c4 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -208,7 +208,6 @@ namespace recfun { bool has_case_def(const symbol& s) const { return m_case_defs.contains(s); } case_def& get_case_def(symbol const& s) { SASSERT(has_case_def(s)); return *(m_case_defs[s]); } bool is_declared(symbol const& s) const { return m_defs.contains(s); } - app_ref mk_depth_limit_pred(unsigned d); private: func_decl * mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range); @@ -274,6 +273,7 @@ namespace recfun { } depth_limit_pred_ref get_depth_limit_pred(unsigned d); + app_ref mk_depth_limit_pred(unsigned d); }; } diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index db9f7d938..b572f0bf7 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -4,10 +4,18 @@ #include "smt/theory_recfun.h" #include "smt/params/smt_params_helper.hpp" -#define DEBUG(x) \ - do { \ - TRACE("recfun", tout << x << '\n';); \ - auto& out = std::cout; out << "recfun: "; out << x; out << '\n' << std::flush; } while(0) +#define DEBUG(x) TRACE("recfun", tout << x << '\n';) + +struct pp_lit { + smt::context & ctx; + smt::literal lit; + pp_lit(smt::context & ctx, smt::literal lit) : ctx(ctx), lit(lit) {} +}; + +std::ostream & operator<<(std::ostream & out, pp_lit const & pp) { + pp.ctx.display_detailed_literal(out, pp.lit); + return out; +} // NOTE: debug struct pp_lits { @@ -76,7 +84,6 @@ namespace smt { } bool theory_recfun::internalize_term(app * term) { - DEBUG("internalizing term: " << mk_pp(term, m())); context & ctx = get_context(); for (expr* e : *term) ctx.internalize(e, false); // the internalization of the arguments may have triggered the internalization of term. @@ -112,11 +119,13 @@ namespace smt { } void theory_recfun::push_scope_eh() { + DEBUG("push_scope"); theory::push_scope_eh(); m_trail.push_scope(); } void theory_recfun::pop_scope_eh(unsigned num_scopes) { + DEBUG("pop_scope"); m_trail.pop_scope(num_scopes); theory::pop_scope_eh(num_scopes); reset_queues(); @@ -151,20 +160,64 @@ namespace smt { m_q_body_expand.clear(); } + class depth_conflict_justification : public justification { + literal_vector lits; + theory_recfun * th; + ast_manager & m() const { return th->get_manager(); } + public: + depth_conflict_justification(theory_recfun * th, region & r, literal_vector const & lits) + : lits(lits), th(th) {} + depth_conflict_justification(depth_conflict_justification const & from) + : lits(from.lits), th(from.th) {} + depth_conflict_justification(depth_conflict_justification && from) + : lits(std::move(from.lits)), th(from.th) {} + char const * get_name() const override { return "depth-conflict"; } + theory_id get_from_theory() const override { return th->get_id(); } + + void get_antecedents(conflict_resolution & cr) override { + auto & ctx = cr.get_context(); + for (unsigned i=0; i < lits.size(); ++i) { + DEBUG("mark literal " << pp_lit(ctx, lits[i])); + cr.mark_literal(lits[i]); + } + } + proof * mk_proof(conflict_resolution & cr) override { + UNREACHABLE(); + /* FIXME: actual proof + app * lemma = m().mk_or(c.size(), c.c_ptr()); + return m().mk_lemma(m().mk_false(), lemma); + */ + } + }; + + void theory_recfun::max_depth_conflict() { DEBUG("max-depth conflict"); - // TODO: build clause from `m_guards` - /* context & ctx = get_context(); - region & r = ctx.get_region(); - ctx.set_conflict( - */ + literal_vector c; + // make clause `depth_limit => V_{g : guards} ~ g` + { + // first literal must be the depth limit one + app_ref dlimit = m_util->mk_depth_limit_pred(get_max_depth()); + ctx.internalize(dlimit, false); + c.push_back(~ ctx.get_literal(dlimit)); + SASSERT(ctx.get_assignment(ctx.get_literal(dlimit)) == l_true); + } + for (auto& kv : m_guards) { + expr * g = & kv.get_key(); + c.push_back(~ ctx.get_literal(g)); + } + DEBUG("max-depth conflict: add clause " << pp_lits(ctx, c.size(), c.c_ptr())); + SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict + region r; + + depth_conflict_justification js(this, r, c); + ctx.set_conflict(ctx.mk_justification(js)); } // if `is_true` and `v = C_f_i(t1…tn)`, then body-expand i-th case of `f(t1…tn)` void theory_recfun::assign_eh(bool_var v, bool is_true) { expr* e = get_context().bool_var2expr(v); - DEBUG("assign_eh "<< mk_pp(e,m())); if (!is_true) return; if (!is_app(e)) return; app* a = to_app(e); @@ -256,7 +309,6 @@ namespace smt { } // assert `p(args) <=> And(guards)` (with CNF on the fly) ctx.internalize(pred_applied, false); - // FIXME: we need to be informed wen `pred_applied` is true!! ctx.mark_as_relevant(ctx.get_bool_var(pred_applied)); literal concl = ctx.get_literal(pred_applied); { @@ -335,8 +387,9 @@ namespace smt { } void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { - DEBUG("add_theory_assumptions"); - // TODO: add depth-limit assumptions? + app_ref dlimit = m_util->mk_depth_limit_pred(get_max_depth()); + DEBUG("add_theory_assumption " << mk_pp(dlimit.get(), m())); + assumptions.push_back(dlimit); } void theory_recfun::display(std::ostream & out) const { From 3b4718b99a9ce60105b6b811100128b4f518b946 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Thu, 14 Dec 2017 19:03:13 +0100 Subject: [PATCH 05/65] simpler conflicts when reaching unrolling limit (just add a clause) --- src/smt/theory_recfun.cpp | 51 +++++++++++---------------------------- src/smt/theory_recfun.h | 1 + 2 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index b572f0bf7..2e81dd188 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -39,7 +39,7 @@ namespace smt { theory_recfun::theory_recfun(ast_manager & m) : theory(m.mk_family_id("recfun")), m_util(0), m_trail(*this), - m_guards(), m_max_depth(0), m_q_case_expand(), m_q_body_expand() + m_guards(), m_max_depth(0), m_q_case_expand(), m_q_body_expand(), m_q_clauses() { recfun_decl_plugin * plugin = reinterpret_cast(m.get_plugin(get_family_id())); @@ -96,6 +96,7 @@ namespace smt { void theory_recfun::reset_queues() { m_q_case_expand.reset(); m_q_body_expand.reset(); + m_q_clauses.reset(); } void theory_recfun::reset_eh() { @@ -137,10 +138,19 @@ namespace smt { } bool theory_recfun::can_propagate() { - return ! (m_q_case_expand.empty() && m_q_body_expand.empty()); + return ! (m_q_case_expand.empty() && + m_q_body_expand.empty() && + m_q_clauses.empty()); } void theory_recfun::propagate() { + context & ctx = get_context(); + + for (literal_vector & c : m_q_clauses) { + ctx.mk_th_axiom(get_id(), c.size(), c.c_ptr()); + } + m_q_clauses.clear(); + for (case_expansion & e : m_q_case_expand) { if (e.m_def->is_fun_macro()) { // body expand immediately @@ -160,37 +170,6 @@ namespace smt { m_q_body_expand.clear(); } - class depth_conflict_justification : public justification { - literal_vector lits; - theory_recfun * th; - ast_manager & m() const { return th->get_manager(); } - public: - depth_conflict_justification(theory_recfun * th, region & r, literal_vector const & lits) - : lits(lits), th(th) {} - depth_conflict_justification(depth_conflict_justification const & from) - : lits(from.lits), th(from.th) {} - depth_conflict_justification(depth_conflict_justification && from) - : lits(std::move(from.lits)), th(from.th) {} - char const * get_name() const override { return "depth-conflict"; } - theory_id get_from_theory() const override { return th->get_id(); } - - void get_antecedents(conflict_resolution & cr) override { - auto & ctx = cr.get_context(); - for (unsigned i=0; i < lits.size(); ++i) { - DEBUG("mark literal " << pp_lit(ctx, lits[i])); - cr.mark_literal(lits[i]); - } - } - proof * mk_proof(conflict_resolution & cr) override { - UNREACHABLE(); - /* FIXME: actual proof - app * lemma = m().mk_or(c.size(), c.c_ptr()); - return m().mk_lemma(m().mk_false(), lemma); - */ - } - }; - - void theory_recfun::max_depth_conflict() { DEBUG("max-depth conflict"); context & ctx = get_context(); @@ -207,12 +186,10 @@ namespace smt { expr * g = & kv.get_key(); c.push_back(~ ctx.get_literal(g)); } - DEBUG("max-depth conflict: add clause " << pp_lits(ctx, c.size(), c.c_ptr())); + DEBUG("max-depth limit: add clause " << pp_lits(ctx, c.size(), c.c_ptr())); SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict - region r; - depth_conflict_justification js(this, r, c); - ctx.set_conflict(ctx.mk_justification(js)); + m_q_clauses.push_back(std::move(c)); } // if `is_true` and `v = C_f_i(t1…tn)`, then body-expand i-th case of `f(t1…tn)` diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 5645a1893..2439c07ff 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -103,6 +103,7 @@ namespace smt { vector m_q_case_expand; vector m_q_body_expand; + vector m_q_clauses; recfun_util & u() const { SASSERT(m_util); return *m_util; } ast_manager & m() { return get_manager(); } From b877bd8286eb9a91b859b9dc14e98eec1ce6ddf0 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Thu, 14 Dec 2017 20:17:02 +0100 Subject: [PATCH 06/65] debug messages and gating --- src/smt/theory_recfun.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 2e81dd188..fdbdeac47 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -4,6 +4,8 @@ #include "smt/theory_recfun.h" #include "smt/params/smt_params_helper.hpp" +#ifdef Z3DEBUG + #define DEBUG(x) TRACE("recfun", tout << x << '\n';) struct pp_lit { @@ -17,7 +19,6 @@ std::ostream & operator<<(std::ostream & out, pp_lit const & pp) { return out; } -// NOTE: debug struct pp_lits { smt::context & ctx; smt::literal *lits; @@ -35,6 +36,14 @@ std::ostream & operator<<(std::ostream & out, pp_lits const & pp) { return out << "}"; } +#else + +#define DEBUG(x) do{}while(0) + +#endif + + + namespace smt { theory_recfun::theory_recfun(ast_manager & m) @@ -147,6 +156,7 @@ namespace smt { context & ctx = get_context(); for (literal_vector & c : m_q_clauses) { + DEBUG("add axiom " << pp_lits(ctx, c.size(), c.c_ptr())); ctx.mk_th_axiom(get_id(), c.size(), c.c_ptr()); } m_q_clauses.clear(); @@ -379,6 +389,7 @@ namespace smt { st.update("recfun body expansion", m_stats.m_body_expansions); } +#ifdef Z3DEBUG std::ostream& operator<<(std::ostream & out, theory_recfun::pp_case_expansion const & e) { return out << "case_exp(" << mk_pp(e.e.m_lhs, e.m) << ")"; } @@ -390,4 +401,5 @@ namespace smt { } return out << ")"; } +#endif } From 0c753aa86ac908af28703a5885a50ddd0e0ce6d1 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Thu, 14 Dec 2017 20:17:11 +0100 Subject: [PATCH 07/65] fix bugs related to backtracking and restarts --- src/smt/theory_recfun.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index fdbdeac47..a910a62ef 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -111,6 +111,8 @@ namespace smt { void theory_recfun::reset_eh() { m_trail.reset(); reset_queues(); + m_stats.reset(); + theory::reset_eh(); } /* @@ -135,15 +137,16 @@ namespace smt { } void theory_recfun::pop_scope_eh(unsigned num_scopes) { - DEBUG("pop_scope"); + DEBUG("pop_scope " << num_scopes); m_trail.pop_scope(num_scopes); theory::pop_scope_eh(num_scopes); reset_queues(); } void theory_recfun::restart_eh() { - m_trail.reset(); + DEBUG("restart"); reset_queues(); + theory::restart_eh(); } bool theory_recfun::can_propagate() { From 35c802d8692a3615a9fa52af0ef69accbccf14aa Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Mon, 25 Dec 2017 17:01:53 +0100 Subject: [PATCH 08/65] simplify and strenghten some code --- src/smt/theory_recfun.cpp | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index a910a62ef..f9e032c87 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -193,14 +193,14 @@ namespace smt { app_ref dlimit = m_util->mk_depth_limit_pred(get_max_depth()); ctx.internalize(dlimit, false); c.push_back(~ ctx.get_literal(dlimit)); - SASSERT(ctx.get_assignment(ctx.get_literal(dlimit)) == l_true); + //SASSERT(ctx.get_assignment(ctx.get_literal(dlimit)) == l_true); } for (auto& kv : m_guards) { expr * g = & kv.get_key(); c.push_back(~ ctx.get_literal(g)); } DEBUG("max-depth limit: add clause " << pp_lits(ctx, c.size(), c.c_ptr())); - SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict + //SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict m_q_clauses.push_back(std::move(c)); } @@ -254,30 +254,21 @@ namespace smt { void theory_recfun::assert_macro_axiom(case_expansion & e) { DEBUG("assert_macro_axiom " << pp_case_expansion(e,m())); SASSERT(e.m_def->is_fun_macro()); - expr * lhs = e.m_lhs; + expr_ref lhs(e.m_lhs, m()); context & ctx = get_context(); auto & vars = e.m_def->get_vars(); // substitute `e.args` into the macro RHS - expr * rhs = apply_args(vars, e.m_args, e.m_def->get_macro_rhs()); + expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m()); DEBUG("macro expansion yields" << mk_pp(rhs,m())); // now build the axiom `lhs = rhs` ctx.internalize(rhs, false); - DEBUG("adding axiom: " << mk_pp(lhs, m()) << " = " << mk_pp(rhs, m())); - if (m().proofs_enabled()) { - // add unit clause `lhs=rhs` - literal l(mk_eq(lhs, rhs, true)); - ctx.mark_as_relevant(l); - literal lits[1] = {l}; - ctx.mk_th_axiom(get_id(), 1, lits); - } - else { - //region r; - enode * e_lhs = ctx.get_enode(lhs); - enode * e_rhs = ctx.get_enode(rhs); - // TODO: proper justification - //justification * js = ctx.mk_justification( - ctx.assign_eq(e_lhs, e_rhs, eq_justification()); - } + // add unit clause `lhs=rhs` + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + literal_vector lits; + lits.push_back(l); + DEBUG("assert_macro_axiom: " << pp_lits(ctx, lits.size(), lits.c_ptr())); + ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); } void theory_recfun::assert_case_axioms(case_expansion & e) { @@ -368,7 +359,7 @@ namespace smt { literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); clause.push_back(l); - DEBUG("assert_body_axiom " << pp_lits(get_context(), clause.size(), clause.c_ptr())); + DEBUG("assert_body_axiom " << pp_lits(ctx, clause.size(), clause.c_ptr())); ctx.mk_th_axiom(get_id(), clause.size(), clause.c_ptr()); } From f7e5977b9e1edf9f15d2d078e5991ecd61c25a73 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Mon, 25 Dec 2017 22:48:37 +0100 Subject: [PATCH 09/65] fix memleak --- src/ast/recfun_decl_plugin.cpp | 6 ++++++ src/ast/recfun_decl_plugin.h | 1 + src/smt/theory_recfun.cpp | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 3ebd888c7..f4b237f9a 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -342,6 +342,12 @@ namespace recfun { m_plugin = dynamic_cast(m.get_plugin(m_family_id)); } + util::~util() { + for (auto & kv : m_dlimit_map) { + dealloc(kv.m_value); + } + } + def * util::decl_fun(symbol const& name, unsigned n, sort *const * domain, sort * range) { return alloc(def, m(), m_family_id, name, n, domain, range); } diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 272ab43c4..206b2c951 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -233,6 +233,7 @@ namespace recfun { void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); public: util(ast_manager &m, family_id); + virtual ~util(); ast_manager & m() { return m_manager; } th_rewriter & get_th_rewriter() { return m_th_rw; } diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index f9e032c87..3bafaf925 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -62,7 +62,7 @@ namespace smt { for (auto & kv : m_guards) { m().dec_ref(kv.m_key); } - m_guards.reset(); + m_guards.reset(); } char const * theory_recfun::get_name() const { return "recfun"; } From cfcff78754e4db5176d7ff7a3e08b9287e070f09 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Mon, 25 Dec 2017 23:35:54 +0100 Subject: [PATCH 10/65] validate unsat cores in recfun --- src/ast/recfun_decl_plugin.h | 1 + src/smt/theory_recfun.cpp | 14 ++++++++++++-- src/smt/theory_recfun.h | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 206b2c951..b51717c1d 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -239,6 +239,7 @@ namespace recfun { th_rewriter & get_th_rewriter() { return m_th_rw; } bool is_case_pred(app * e) const { return is_app_of(e, m_family_id, OP_FUN_CASE_PRED); } bool is_defined(app * e) const { return is_app_of(e, m_family_id, OP_FUN_DEFINED); } + bool is_depth_limit(app * e) const { return is_app_of(e, m_family_id, OP_DEPTH_LIMIT); } bool owns_app(app * e) const { return e->get_family_id() == m_family_id; } //mk_depth_limit_pred(get_max_depth()); ctx.internalize(dlimit, false); c.push_back(~ ctx.get_literal(dlimit)); - //SASSERT(ctx.get_assignment(ctx.get_literal(dlimit)) == l_true); + SASSERT(ctx.get_assignment(ctx.get_literal(dlimit)) == l_true); } for (auto& kv : m_guards) { expr * g = & kv.get_key(); c.push_back(~ ctx.get_literal(g)); } DEBUG("max-depth limit: add clause " << pp_lits(ctx, c.size(), c.c_ptr())); - //SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict + SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict m_q_clauses.push_back(std::move(c)); } @@ -373,6 +373,16 @@ namespace smt { assumptions.push_back(dlimit); } + + // if `dlimit` occurs in unsat core, return "unknown" + lbool theory_recfun::validate_unsat_core(expr_ref_vector & unsat_core) { + for (auto & e : unsat_core) { + if (is_app(e) && m_util->is_depth_limit(to_app(e))) + return l_undef; + } + return l_false; + } + void theory_recfun::display(std::ostream & out) const { out << "recfun{}"; } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 2439c07ff..e73b43a7d 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -136,6 +136,7 @@ namespace smt { void restart_eh() override; bool can_propagate() override; void propagate() override; + lbool validate_unsat_core(expr_ref_vector &) override; void new_eq_eh(theory_var v1, theory_var v2) override {} void new_diseq_eh(theory_var v1, theory_var v2) override {} From 2b968f9e6348a04893c2787ca3f66f1a27c61bc6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 9 Aug 2018 15:29:39 -0700 Subject: [PATCH 11/65] initial decl plugin Signed-off-by: Nikolaj Bjorner --- src/ast/CMakeLists.txt | 1 + src/ast/jobshop_decl_plugin.cpp | 178 ++++++++++++++++++++++++++++++++ src/ast/jobshop_decl_plugin.h | 129 +++++++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 src/ast/jobshop_decl_plugin.cpp create mode 100644 src/ast/jobshop_decl_plugin.h diff --git a/src/ast/CMakeLists.txt b/src/ast/CMakeLists.txt index 80543bb05..5340d4e7b 100644 --- a/src/ast/CMakeLists.txt +++ b/src/ast/CMakeLists.txt @@ -30,6 +30,7 @@ z3_add_component(ast fpa_decl_plugin.cpp func_decl_dependencies.cpp has_free_vars.cpp + jobshop_decl_plugin.cpp macro_substitution.cpp num_occurs.cpp occurs.cpp diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/jobshop_decl_plugin.cpp new file mode 100644 index 000000000..41d8ad241 --- /dev/null +++ b/src/ast/jobshop_decl_plugin.cpp @@ -0,0 +1,178 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + jobshop_decl_plugin.h + +Abstract: + + Declarations used for a job-shop scheduling domain. + +Author: + + Nikolaj Bjorner (nbjorner) 2018-8-9 + +Revision History: + + +--*/ +#include "ast/jobshop_decl_plugin.h" +#include "ast/arith_decl_plugin.h" + +void jobshop_decl_plugin::set_manager(ast_manager* m, family_id fid) { + decl_plugin::set_manager(m, fid); + m_int_sort = m_manager->mk_sort(m_manager->mk_family_id("arith"), INT_SORT); + m_job_sort = m_manager->mk_sort(symbol("Job"), sort_info(m_family_id, JOB_SORT)); + m_resource_sort = m_manager->mk_sort(symbol("Resource"), sort_info(m_family_id, RESOURCE_SORT)); + m_manager->inc_ref(m_int_sort); + m_manager->inc_ref(m_resource_sort); + m_manager->inc_ref(m_job_sort); +} + +void jobshop_decl_plugin::finalize() { + m_manager->dec_ref(m_job_sort); + m_manager->dec_ref(m_resource_sort); + m_manager->dec_ref(m_int_sort); +} + +sort * jobshop_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + if (num_parameters != 0) { + m_manager->raise_exception("no parameters expected with job-shop sort"); + } + switch (static_cast(k)) { + case JOB_SORT: return m_job_sort; + case RESOURCE_SORT: return m_resource_sort; + default: UNREACHABLE(); return nullptr; + } +} + +func_decl * jobshop_decl_plugin::mk_func_decl( + decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const *, sort *) { + switch (static_cast(k)) { + case OP_JS_JOB: + check_arity(arity); + check_index1(num_parameters, parameters); + return m_manager->mk_func_decl(symbol("job"), 0, (sort* const*)nullptr, m_job_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_RESOURCE: + check_arity(arity); + check_index1(num_parameters, parameters); + return m_manager->mk_func_decl(symbol("resource"), 0, (sort* const*)nullptr, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_START: + check_arity(arity); + check_index1(num_parameters, parameters); + return m_manager->mk_func_decl(symbol("job-start"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_END: + check_arity(arity); + check_index1(num_parameters, parameters); + return m_manager->mk_func_decl(symbol("job-end"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_JOB2RESOURCE: + check_arity(arity); + check_index1(num_parameters, parameters); + return m_manager->mk_func_decl(symbol("job2resource"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_JOB_ON_RESOURCE: + check_arity(arity); + check_index1(num_parameters, parameters); + return m_manager->mk_func_decl(symbol("job-on-resource"), 0, (sort* const*)nullptr, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k, num_parameters, parameters)); + default: + UNREACHABLE(); return nullptr; + } +} + +void jobshop_decl_plugin::check_arity(unsigned arity) { + if (arity > 0) + m_manager->raise_exception("jobshop variables use parameters only and take no arguments"); +} + +void jobshop_decl_plugin::check_index1(unsigned num_parameters, parameter const* ps) { + if (num_parameters != 1 || !ps[0].is_int()) + m_manager->raise_exception("jobshop variable expects a single integer parameter"); +} + +void jobshop_decl_plugin::check_index2(unsigned num_parameters, parameter const* ps) { + if (num_parameters != 2 || !ps[0].is_int() || !ps[1].is_int()) + m_manager->raise_exception("jobshop variable expects two integer parameters"); +} + + +bool jobshop_decl_plugin::is_value(app * e) const { + return is_app_of(e, m_family_id, OP_JS_JOB) || is_app_of(e, m_family_id, OP_JS_RESOURCE); +} + +void jobshop_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + if (logic == symbol("JOBSHOP")) { + op_names.push_back(builtin_name("job", OP_JS_JOB)); + op_names.push_back(builtin_name("resource", OP_JS_RESOURCE)); + op_names.push_back(builtin_name("job-start", OP_JS_START)); + op_names.push_back(builtin_name("job-end", OP_JS_END)); + op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE)); + op_names.push_back(builtin_name("job-on-resource", OP_JS_JOB_ON_RESOURCE)); + } +} + +void jobshop_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + if (logic == symbol("JOBSHOP")) { + sort_names.push_back(builtin_name("Job", JOB_SORT)); + sort_names.push_back(builtin_name("Resource", RESOURCE_SORT)); + } +} + +expr * jobshop_decl_plugin::get_some_value(sort * s) { + parameter p(0); + if (is_sort_of(s, m_family_id, JOB_SORT)) + return m_manager->mk_const(mk_func_decl(OP_JS_JOB, 1, &p, 0, nullptr, nullptr)); + if (is_sort_of(s, m_family_id, RESOURCE_SORT)) + return m_manager->mk_const(mk_func_decl(OP_JS_RESOURCE, 1, &p, 0, nullptr, nullptr)); + UNREACHABLE(); + return nullptr; +} + + +jobshop_util::jobshop_util(ast_manager& m): m(m) { + m_fid = m.mk_family_id("jobshop"); + m_plugin = static_cast(m.get_plugin(m_fid)); +} + +sort* jobshop_util::mk_job_sort() { + return m_plugin->mk_job_sort(); +} + +sort* jobshop_util::mk_resource_sort() { + return m_plugin->mk_resource_sort(); +} + +app* jobshop_util::mk_job(unsigned j) { + parameter p(j); + return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB, 1, &p, 0, (sort*const*)nullptr, nullptr)); +} + +unsigned jobshop_util::job2id(expr* j) { + SASSERT(is_app_of(j, m_fid, OP_JS_JOB)); + return to_app(j)->get_decl()->get_parameter(0).get_int(); +} + +app* jobshop_util::mk_resource(unsigned r) { + parameter p(r); + return m.mk_const(m.mk_func_decl(m_fid, OP_JS_RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr)); +} + +unsigned jobshop_util::resource2id(expr* r) { + SASSERT(is_app_of(r, m_fid, OP_JS_RESOURCE)); + return to_app(r)->get_decl()->get_parameter(0).get_int(); +} + +app* jobshop_util::mk_start(unsigned j) { + parameter p(j); + return m.mk_const(m.mk_func_decl(m_fid, OP_JS_START, 1, &p, 0, (sort*const*)nullptr, nullptr)); +} + +app* jobshop_util::mk_end(unsigned j) { + parameter p(j); + return m.mk_const(m.mk_func_decl(m_fid, OP_JS_END, 1, &p, 0, (sort*const*)nullptr, nullptr)); +} + +app* jobshop_util::mk_on_resource(unsigned j) { + parameter p(j); + return m.mk_const(m.mk_func_decl(m_fid, OP_JS_END, 1, &p, 0, (sort*const*)nullptr, nullptr)); +} + diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/jobshop_decl_plugin.h new file mode 100644 index 000000000..94936c5aa --- /dev/null +++ b/src/ast/jobshop_decl_plugin.h @@ -0,0 +1,129 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + jobshop_decl_plugin.h + +Abstract: + + Declarations used for a job-shop scheduling domain. + + The job-shop domain comprises of constants job(j), resource(r) + + It finds values to variables: + - start(j), end(j), job2resource(j), job-on-resource(j, r) + + It assumes a background of: + - resources : Job -> Resource -> Int * LoadPct - time to run job j on resource r assuming LoadPct + - runtime : Job -> Int - time to run job j if not associated with any resource + - capacity : Resource -> Int -> LoadPct - capacity of resource r at time t, given as sequence of time intervals + + // Theory: + job-on-resource(j) => resource(j) in dom j.resources; + end(j) - start(j) = time-to-execute(j) + time-to-execute(j) := runtime(j) if !job-on-resource(j) + time-to-execute(j) := time-to-execute(j, resource(j)) otherwise + + time-to-execute(j, r) := (T - start(j)) + where capacity(j,r) = sum_{t = start(j)}^{T} load(loadpct(j,r), r, t) + + capacity(j, r) := cap where (cap, loadpct) = resources j r + loadpct(j, r) := loadpct where (cap, loadpct) = resources j r + + load(loadpct, r, t) := min(capacity r t, loadpct) / loadpct + + capacity(r, t) >= sum_{j | job-on-resource(j, r, t) } min(capacity r t, loadpct(j, r)) + + // Macros: + job-on-resource(j, r) := (job-on-resource(j) & r = resource(j)); + job-on-resource(j, r, t) := (job-on-resource(j, r) & start(j) <= t <= end(j)); + start_min(j, t) := start(j) >= t; + end_max(j, t) := end(j) <= t; + job_link(j1, j2, startstart, hard) := start(j1) = start(j2); + job_link(j1, j2, startstart, soft) := start(j1) <= start(j2); + job_link(j1, j2, endend, hard) := end(j1) = end(j2); + job_link(j1, j2, endend, soft) := end(j2) <= end(j1); + job_link(j1, j2, endstart, hard) := end(j1) = start(j2); + job_link(j1, j2, endstart, soft) := end(j2) <= start(j1); + job_link(j1, j2, startend, hard) := end(j2) = start(j1); + job_link(j1, j2, startend, soft) := end(j1) <= start(j2); + job_delay(j1, j2, t) := end(j1) + t <= end(j2); + job_on_same_resource(j1, j2) := job-on-resource(j1) & job-on-resource(j2) & resource(j1) = resource(j2); + job_not_on_same_resource(j1, j2) := !(job-on-resource(j1) & job-on-resource(j2) & resource(j1) = resource(j2)); + job_time_intersect(j1, j2) := start(j1) <= end(j2) <= end(j1) || start(j2) <= end(j1) <= end(j2); + run_time_bound(j) := !(job-on-resource(j)); + + job-on-resource(j, r, t) => job-property(j) = null or job_property(j) in working_time_property(r, t); + +Author: + + Nikolaj Bjorner (nbjorner) 2018-8-9 + +Revision History: + + +--*/ +#pragma once; +#include "ast/ast.h" + +enum js_sort_kind { + JOB_SORT, + RESOURCE_SORT +}; + +enum js_op_kind { + OP_JS_JOB, // value of type job + OP_JS_RESOURCE, // value of type resource + OP_JS_START, // start time of a job + OP_JS_END, // end time of a job + OP_JS_JOB2RESOURCE, // resource associated with job + OP_JS_JOB_ON_RESOURCE // is a job associated with a resource +}; + +class jobshop_decl_plugin : public decl_plugin { +public: + jobshop_decl_plugin() {} + ~jobshop_decl_plugin() override {} + void finalize() override; + void set_manager(ast_manager* m, family_id fid) override; + decl_plugin * mk_fresh() override { return alloc(jobshop_decl_plugin); } + sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override; + func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) override; + bool is_value(app * e) const override; + bool is_unique_value(app * e) const override { return is_value(e); } + void get_op_names(svector & op_names, symbol const & logic) override; + void get_sort_names(svector & sort_names, symbol const & logic) override; + expr * get_some_value(sort * s) override; + sort * mk_job_sort() const { return m_job_sort; } + sort * mk_resource_sort() const { return m_resource_sort; } +private: + sort* m_job_sort; + sort* m_resource_sort; + sort* m_int_sort; + + void check_arity(unsigned arity); + void check_index1(unsigned n, parameter const* ps); + void check_index2(unsigned n, parameter const* ps); +}; + +class jobshop_util { + ast_manager& m; + family_id m_fid; + jobshop_decl_plugin* m_plugin; +public: + jobshop_util(ast_manager& m); + sort* mk_job_sort(); + sort* mk_resource_sort(); + + app* mk_job(unsigned j); + unsigned job2id(expr* j); + + app* mk_resource(unsigned r); + unsigned resource2id(expr* r); + + app* mk_start(unsigned j); + app* mk_end(unsigned j); + app* mk_on_resource(unsigned j); +}; From 0d8de8f65fd4d3500d1db093bd5148d645bbfb97 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 9 Aug 2018 20:19:26 -0700 Subject: [PATCH 12/65] add theory outlline Signed-off-by: Nikolaj Bjorner --- src/ast/jobshop_decl_plugin.cpp | 10 --- src/ast/jobshop_decl_plugin.h | 21 ++--- src/smt/CMakeLists.txt | 1 + src/smt/theory_jobscheduler.cpp | 146 ++++++++++++++++++++++++++++++++ src/smt/theory_jobscheduler.h | 124 +++++++++++++++++++++++++++ 5 files changed, 280 insertions(+), 22 deletions(-) create mode 100644 src/smt/theory_jobscheduler.cpp create mode 100644 src/smt/theory_jobscheduler.h diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/jobshop_decl_plugin.cpp index 41d8ad241..ced77690d 100644 --- a/src/ast/jobshop_decl_plugin.cpp +++ b/src/ast/jobshop_decl_plugin.cpp @@ -70,10 +70,6 @@ func_decl * jobshop_decl_plugin::mk_func_decl( check_arity(arity); check_index1(num_parameters, parameters); return m_manager->mk_func_decl(symbol("job2resource"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); - case OP_JS_JOB_ON_RESOURCE: - check_arity(arity); - check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job-on-resource"), 0, (sort* const*)nullptr, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k, num_parameters, parameters)); default: UNREACHABLE(); return nullptr; } @@ -106,7 +102,6 @@ void jobshop_decl_plugin::get_op_names(svector & op_names, symbol op_names.push_back(builtin_name("job-start", OP_JS_START)); op_names.push_back(builtin_name("job-end", OP_JS_END)); op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE)); - op_names.push_back(builtin_name("job-on-resource", OP_JS_JOB_ON_RESOURCE)); } } @@ -171,8 +166,3 @@ app* jobshop_util::mk_end(unsigned j) { return m.mk_const(m.mk_func_decl(m_fid, OP_JS_END, 1, &p, 0, (sort*const*)nullptr, nullptr)); } -app* jobshop_util::mk_on_resource(unsigned j) { - parameter p(j); - return m.mk_const(m.mk_func_decl(m_fid, OP_JS_END, 1, &p, 0, (sort*const*)nullptr, nullptr)); -} - diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/jobshop_decl_plugin.h index 94936c5aa..6a78646b0 100644 --- a/src/ast/jobshop_decl_plugin.h +++ b/src/ast/jobshop_decl_plugin.h @@ -12,17 +12,17 @@ Abstract: The job-shop domain comprises of constants job(j), resource(r) It finds values to variables: - - start(j), end(j), job2resource(j), job-on-resource(j, r) + - start(j), end(j), job2resource(j) It assumes a background of: - resources : Job -> Resource -> Int * LoadPct - time to run job j on resource r assuming LoadPct - runtime : Job -> Int - time to run job j if not associated with any resource - capacity : Resource -> Int -> LoadPct - capacity of resource r at time t, given as sequence of time intervals - + // assume each job has at least one resource associated with it. + // introduce a dummy resource if needed. + // Theory: - job-on-resource(j) => resource(j) in dom j.resources; end(j) - start(j) = time-to-execute(j) - time-to-execute(j) := runtime(j) if !job-on-resource(j) time-to-execute(j) := time-to-execute(j, resource(j)) otherwise time-to-execute(j, r) := (T - start(j)) @@ -36,7 +36,7 @@ Abstract: capacity(r, t) >= sum_{j | job-on-resource(j, r, t) } min(capacity r t, loadpct(j, r)) // Macros: - job-on-resource(j, r) := (job-on-resource(j) & r = resource(j)); + job-on-resource(j, r) := r = resource(j); job-on-resource(j, r, t) := (job-on-resource(j, r) & start(j) <= t <= end(j)); start_min(j, t) := start(j) >= t; end_max(j, t) := end(j) <= t; @@ -49,11 +49,10 @@ Abstract: job_link(j1, j2, startend, hard) := end(j2) = start(j1); job_link(j1, j2, startend, soft) := end(j1) <= start(j2); job_delay(j1, j2, t) := end(j1) + t <= end(j2); - job_on_same_resource(j1, j2) := job-on-resource(j1) & job-on-resource(j2) & resource(j1) = resource(j2); - job_not_on_same_resource(j1, j2) := !(job-on-resource(j1) & job-on-resource(j2) & resource(j1) = resource(j2)); + job_on_same_resource(j1, j2) := resource(j1) = resource(j2); + job_not_on_same_resource(j1, j2) := resource(j1) != resource(j2); job_time_intersect(j1, j2) := start(j1) <= end(j2) <= end(j1) || start(j2) <= end(j1) <= end(j2); - run_time_bound(j) := !(job-on-resource(j)); - + job-on-resource(j, r, t) => job-property(j) = null or job_property(j) in working_time_property(r, t); Author: @@ -77,8 +76,7 @@ enum js_op_kind { OP_JS_RESOURCE, // value of type resource OP_JS_START, // start time of a job OP_JS_END, // end time of a job - OP_JS_JOB2RESOURCE, // resource associated with job - OP_JS_JOB_ON_RESOURCE // is a job associated with a resource + OP_JS_JOB2RESOURCE // resource associated with job }; class jobshop_decl_plugin : public decl_plugin { @@ -125,5 +123,4 @@ public: app* mk_start(unsigned j); app* mk_end(unsigned j); - app* mk_on_resource(unsigned j); }; diff --git a/src/smt/CMakeLists.txt b/src/smt/CMakeLists.txt index fb1997fb4..13a00d44e 100644 --- a/src/smt/CMakeLists.txt +++ b/src/smt/CMakeLists.txt @@ -55,6 +55,7 @@ z3_add_component(smt theory_dl.cpp theory_dummy.cpp theory_fpa.cpp + theory_jobscheduler.cpp theory_lra.cpp theory_opt.cpp theory_pb.cpp diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp new file mode 100644 index 000000000..bf49a00fb --- /dev/null +++ b/src/smt/theory_jobscheduler.cpp @@ -0,0 +1,146 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + theory_jobscheduler.cpp + +Abstract: + + +Author: + + Nikolaj Bjorner (nbjorner) 2018-09-08. + +Revision History: + +--*/ + +#include "smt/theory_jobscheduler.h" +#include "smt/smt_context.h" + +namespace smt { + + theory_var theory_jobscheduler::mk_var(enode * n) { + theory_var v = theory::mk_var(n); + return v; + } + + bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) { + return false; + } + + bool theory_jobscheduler::internalize_term(app * term) { + return false; + } + + void theory_jobscheduler::assign_eh(bool_var v, bool is_true) { + + } + + void theory_jobscheduler::new_eq_eh(theory_var v1, theory_var v2) { + + } + + void theory_jobscheduler::new_diseq_eh(theory_var v1, theory_var v2) { + + } + + void theory_jobscheduler::push_scope_eh() { + + } + + void theory_jobscheduler::pop_scope_eh(unsigned num_scopes) { + + } + + final_check_status theory_jobscheduler::final_check_eh() { + return FC_DONE; + } + + bool theory_jobscheduler::can_propagate() { + return false; + } + + void theory_jobscheduler::propagate() { + + } + + theory_jobscheduler::theory_jobscheduler(ast_manager& m): theory(m.get_family_id("jobshop")), m(m), u(m) { + + } + + void theory_jobscheduler::display(std::ostream & out) const { + + } + + void theory_jobscheduler::collect_statistics(::statistics & st) const { + + } + + void theory_jobscheduler::init_model(model_generator & m) { + + } + + model_value_proc * theory_jobscheduler::mk_value(enode * n, model_generator & mg) { + return nullptr; + } + + bool theory_jobscheduler::get_value(enode * n, expr_ref & r) { + return false; + } + + theory * theory_jobscheduler::mk_fresh(context * new_ctx) { + return alloc(theory_jobscheduler, new_ctx->get_manager()); + } + + uint64_t theory_jobscheduler::est(unsigned j) { + return 0; + } + + uint64_t theory_jobscheduler::lst(unsigned j) { + return 0; + } + + uint64_t theory_jobscheduler::ect(unsigned j) { + return 0; + } + + uint64_t theory_jobscheduler::lct(unsigned j) { + return 0; + } + + uint64_t theory_jobscheduler::start(unsigned j) { + return 0; + } + + uint64_t theory_jobscheduler::end(unsigned j) { + return 0; + } + + unsigned theory_jobscheduler::resource(unsigned j) { + return 0; + } + + + void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, uint64_t end) { + m_jobs.reserve(j + 1); + m_resources.reserve(r + 1); + job_info& ji = m_jobs[j]; + if (ji.m_resource2index.contains(r)) { + throw default_exception("resource already bound to job"); + } + ji.m_resource2index.insert(r, ji.m_resources.size()); + ji.m_resources.push_back(job_resource(r, cap, loadpct, end)); + SASSERT(!m_resources[r].m_jobs.contains(j)); + m_resources[r].m_jobs.push_back(j); + } + + void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, uint64_t start, uint64_t end) { + SASSERT(start < end); + m_resources.reserve(r + 1); + m_resources[r].m_available.push_back(res_available(max_loadpct, start, end)); + } + +}; + diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h new file mode 100644 index 000000000..7e93e3454 --- /dev/null +++ b/src/smt/theory_jobscheduler.h @@ -0,0 +1,124 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + theory_jobscheduling.h + +Abstract: + + Propagation solver for jobscheduling problems. + It relies on an external module to tighten bounds of + job variables. + +Author: + + Nikolaj Bjorner (nbjorner) 2018-09-08. + +Revision History: + +--*/ +#pragma once; + +#include "smt/smt_theory.h" +#include "ast/jobshop_decl_plugin.h" + +namespace smt { + + class theory_jobscheduler : public theory { + + struct job_resource { + unsigned m_resource_id; // id of resource + unsigned m_capacity; // amount of resource to use + unsigned m_loadpct; // assuming loadpct + uint64_t m_end; // must run before + job_resource(unsigned r, unsigned cap, unsigned loadpct, uint64_t end): + m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_end(end) {} + }; + + struct job_info { + vector m_resources; // resources allowed to run job. + u_map m_resource2index; // resource to index into vector + }; + + struct res_available { + unsigned m_loadpct; + uint64_t m_start; + uint64_t m_end; + res_available(unsigned load_pct, uint64_t start, uint64_t end): + m_loadpct(load_pct), + m_start(start), + m_end(end) + {} + }; + + struct res_info { + unsigned_vector m_jobs; // jobs allocated to run on resource + vector m_available; // time intervals where resource is available + uint64_t m_end; // can't run after + res_info(): m_end(std::numeric_limits::max()) {} + }; + + ast_manager& m; + jobshop_util u; + vector m_jobs; + vector m_resources; + + protected: + + theory_var mk_var(enode * n) override; + + bool internalize_atom(app * atom, bool gate_ctx) override; + + bool internalize_term(app * term) override; + + void assign_eh(bool_var v, bool is_true) override; + + void new_eq_eh(theory_var v1, theory_var v2) override; + + void new_diseq_eh(theory_var v1, theory_var v2) override; + + void push_scope_eh() override; + + void pop_scope_eh(unsigned num_scopes) override; + + final_check_status final_check_eh() override; + + bool can_propagate() override; + + void propagate() override; + + public: + + theory_jobscheduler(ast_manager& m); + + ~theory_jobscheduler() override {} + + void display(std::ostream & out) const override; + + void collect_statistics(::statistics & st) const override; + + void init_model(model_generator & m) override; + + model_value_proc * mk_value(enode * n, model_generator & mg) override; + + bool get_value(enode * n, expr_ref & r) override; + + theory * mk_fresh(context * new_ctx) override; // { return alloc(theory_jobscheduler, new_ctx->get_manager()); } + + public: + // assignments: + uint64_t est(unsigned j); // earliest start time of job j + uint64_t lst(unsigned j); // last start time + uint64_t ect(unsigned j); // earliest completion time + uint64_t lct(unsigned j); // last completion time + uint64_t start(unsigned j); // start time of job j + uint64_t end(unsigned j); // end time of job j + unsigned resource(unsigned j); // resource of job j + + // set up model + void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, uint64_t end); + void add_resource_available(unsigned r, unsigned max_loadpct, uint64_t start, uint64_t end); + }; +}; + From baeff82e5995dd9d7da9bd2a5095c6833fa1c8fc Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 10 Aug 2018 09:46:21 -0700 Subject: [PATCH 13/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/jobshop_decl_plugin.cpp | 10 +++++- src/ast/jobshop_decl_plugin.h | 1 + src/smt/theory_jobscheduler.cpp | 61 +++++++++++++++++++++++---------- src/smt/theory_jobscheduler.h | 13 ++++--- 4 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/jobshop_decl_plugin.cpp index ced77690d..7a7b1b590 100644 --- a/src/ast/jobshop_decl_plugin.cpp +++ b/src/ast/jobshop_decl_plugin.cpp @@ -142,7 +142,10 @@ app* jobshop_util::mk_job(unsigned j) { } unsigned jobshop_util::job2id(expr* j) { - SASSERT(is_app_of(j, m_fid, OP_JS_JOB)); + SASSERT(is_app_of(j, m_fid, OP_JS_JOB) || + is_app_of(j, m_fid, OP_JS_START) || + is_app_of(j, m_fid, OP_JS_END) || + is_app_of(j, m_fid, OP_JS_JOB2RESOURCE)); return to_app(j)->get_decl()->get_parameter(0).get_int(); } @@ -166,3 +169,8 @@ app* jobshop_util::mk_end(unsigned j) { return m.mk_const(m.mk_func_decl(m_fid, OP_JS_END, 1, &p, 0, (sort*const*)nullptr, nullptr)); } +app* jobshop_util::mk_job2resource(unsigned j) { + parameter p(j); + return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr)); +} + diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/jobshop_decl_plugin.h index 6a78646b0..48bdde6c1 100644 --- a/src/ast/jobshop_decl_plugin.h +++ b/src/ast/jobshop_decl_plugin.h @@ -123,4 +123,5 @@ public: app* mk_start(unsigned j); app* mk_end(unsigned j); + app* mk_job2resource(unsigned j); }; diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index bf49a00fb..f8439917e 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -26,32 +26,55 @@ namespace smt { return v; } - bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) { - return false; - } - bool theory_jobscheduler::internalize_term(app * term) { - return false; + context & ctx = get_context(); + if (ctx.e_internalized(term)) + return true; + enode* e = ctx.mk_enode(term, false, false, true); + switch (static_cast(term->get_decl()->get_decl_kind())) { + case OP_JS_JOB: { + unsigned j = u.job2id(term); + app_ref start(u.mk_start(j), m); + app_ref end(u.mk_end(j), m); + app_ref res(u.mk_resource(j), m); + if (!ctx.e_internalized(start)) ctx.internalize(start, false); + if (!ctx.e_internalized(end)) ctx.internalize(end, false); + if (!ctx.e_internalized(res)) ctx.internalize(res, false); + theory_var v = mk_var(e); + SASSERT(m_var2index.size() == v); + m_var2index.push_back(j); + m_jobs.reserve(j + 1); + m_jobs[j].m_start = ctx.get_enode(start); + m_jobs[j].m_end = ctx.get_enode(end); + m_jobs[j].m_resource = ctx.get_enode(res); + ctx.attach_th_var(e, this, v); + break; + } + case OP_JS_RESOURCE: { + theory_var v = mk_var(e); + SASSERT(m_var2index.size() == v); + unsigned r = u.resource2id(term); + m_var2index.push_back(r); + ctx.attach_th_var(e, this, v); + break; + } + case OP_JS_START: + case OP_JS_END: + case OP_JS_JOB2RESOURCE: { + unsigned j = u.job2id(term); + app_ref job(u.mk_job(j), m); + if (!ctx.e_internalized(job)) ctx.internalize(job, false); + break; + } + } + return true; } - void theory_jobscheduler::assign_eh(bool_var v, bool is_true) { - - } - - void theory_jobscheduler::new_eq_eh(theory_var v1, theory_var v2) { - - } - - void theory_jobscheduler::new_diseq_eh(theory_var v1, theory_var v2) { - - } void theory_jobscheduler::push_scope_eh() { - } void theory_jobscheduler::pop_scope_eh(unsigned num_scopes) { - } final_check_status theory_jobscheduler::final_check_eh() { @@ -124,6 +147,7 @@ namespace smt { void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, uint64_t end) { + // assert: done at base level m_jobs.reserve(j + 1); m_resources.reserve(r + 1); job_info& ji = m_jobs[j]; @@ -137,6 +161,7 @@ namespace smt { } void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, uint64_t start, uint64_t end) { + // assert: done at base level SASSERT(start < end); m_resources.reserve(r + 1); m_resources[r].m_available.push_back(res_available(max_loadpct, start, end)); diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 7e93e3454..ec09ce0ce 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -39,6 +39,10 @@ namespace smt { struct job_info { vector m_resources; // resources allowed to run job. u_map m_resource2index; // resource to index into vector + enode* m_start; + enode* m_end; + enode* m_resource; + job_info(): m_start(nullptr), m_end(nullptr), m_resource(nullptr) {} }; struct res_available { @@ -61,6 +65,7 @@ namespace smt { ast_manager& m; jobshop_util u; + unsigned_vector m_var2index; vector m_jobs; vector m_resources; @@ -68,15 +73,15 @@ namespace smt { theory_var mk_var(enode * n) override; - bool internalize_atom(app * atom, bool gate_ctx) override; + bool internalize_atom(app * atom, bool gate_ctx) override { return false; } bool internalize_term(app * term) override; - void assign_eh(bool_var v, bool is_true) override; + void assign_eh(bool_var v, bool is_true) override {} - void new_eq_eh(theory_var v1, theory_var v2) override; + void new_eq_eh(theory_var v1, theory_var v2) override {} - void new_diseq_eh(theory_var v1, theory_var v2) override; + void new_diseq_eh(theory_var v1, theory_var v2) override {} void push_scope_eh() override; From 55f15b09214404a80f7918d2aaa15cc9b48c7cbe Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 10 Aug 2018 17:52:34 -0700 Subject: [PATCH 14/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/arith_decl_plugin.h | 3 + src/smt/theory_jobscheduler.cpp | 232 ++++++++++++++++++++++++++++++-- src/smt/theory_jobscheduler.h | 66 ++++++--- 3 files changed, 273 insertions(+), 28 deletions(-) diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 09f082522..aea8863af 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -361,6 +361,9 @@ public: app * mk_int(int i) { return mk_numeral(rational(i), true); } + app * mk_int(rational const& r) { + return mk_numeral(r, true); + } app * mk_real(int i) { return mk_numeral(rational(i), false); } diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index f8439917e..d2cab8bf8 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -89,12 +89,41 @@ namespace smt { } - theory_jobscheduler::theory_jobscheduler(ast_manager& m): theory(m.get_family_id("jobshop")), m(m), u(m) { + theory_jobscheduler::theory_jobscheduler(ast_manager& m): + theory(m.get_family_id("jobshop")), m(m), u(m), a(m) { } - - void theory_jobscheduler::display(std::ostream & out) const { + std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { + return out << " r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_end << "\n"; + } + + std::ostream& theory_jobscheduler::display(std::ostream & out, job_info const& j) const { + for (job_resource const& jr : j.m_resources) { + display(out, jr); + } + return out; + } + + std::ostream& theory_jobscheduler::display(std::ostream & out, res_available const& r) const { + out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%%\n"; + return out; + } + + std::ostream& theory_jobscheduler::display(std::ostream & out, res_info const& r) const { + for (res_available const& ra : r.m_available) { + display(out, ra); + } + return out; + } + + void theory_jobscheduler::display(std::ostream & out) const { + for (unsigned j = 0; j < m_jobs.size(); ++j) { + display(out << "job " << j << ":\n", m_jobs[j]); + } + for (unsigned r = 0; r < m_resources.size(); ++r) { + display(out << "resource " << r << ":\n", m_resources[r]); + } } void theory_jobscheduler::collect_statistics(::statistics & st) const { @@ -117,27 +146,27 @@ namespace smt { return alloc(theory_jobscheduler, new_ctx->get_manager()); } - uint64_t theory_jobscheduler::est(unsigned j) { + time_t theory_jobscheduler::est(unsigned j) { return 0; } - uint64_t theory_jobscheduler::lst(unsigned j) { + time_t theory_jobscheduler::lst(unsigned j) { return 0; } - uint64_t theory_jobscheduler::ect(unsigned j) { + time_t theory_jobscheduler::ect(unsigned j) { return 0; } - uint64_t theory_jobscheduler::lct(unsigned j) { + time_t theory_jobscheduler::lct(unsigned j) { return 0; } - uint64_t theory_jobscheduler::start(unsigned j) { + time_t theory_jobscheduler::start(unsigned j) { return 0; } - uint64_t theory_jobscheduler::end(unsigned j) { + time_t theory_jobscheduler::end(unsigned j) { return 0; } @@ -146,8 +175,8 @@ namespace smt { } - void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, uint64_t end) { - // assert: done at base level + void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end) { + SASSERT(get_context().at_base_level()); m_jobs.reserve(j + 1); m_resources.reserve(r + 1); job_info& ji = m_jobs[j]; @@ -160,12 +189,189 @@ namespace smt { m_resources[r].m_jobs.push_back(j); } - void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, uint64_t start, uint64_t end) { - // assert: done at base level + void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end) { + SASSERT(get_context().at_base_level()); SASSERT(start < end); m_resources.reserve(r + 1); m_resources[r].m_available.push_back(res_available(max_loadpct, start, end)); } + + /* + * Initialze the state based on the set of jobs and resources added. + * For each job j, with possible resources r1, ..., r_n assert + * resource(j) = r_1 || resource(j) = r_2 || ... || resource(j) = r_n + * For each job and resource r with deadline end(j,r) assert + * resource(j) = r => end(j) <= end(j,r) + * + * Ensure that the availability slots for each resource is sorted by time. + */ + void theory_jobscheduler::add_done() { + context & ctx = get_context(); + for (unsigned j = 0; j < m_jobs.size(); ++j) { + job_info const& ji = m_jobs[j]; + expr_ref_vector disj(m); + app_ref job(u.mk_job(j), m); + if (ji.m_resources.empty()) { + throw default_exception("every job should be associated with at least one resource"); + } + // resource(j) = r => end(j) <= end(j, r) + for (job_resource const& jr : ji.m_resources) { + app_ref res(u.mk_resource(jr.m_resource_id), m); + expr_ref eq(m.mk_eq(job, res), m); + expr_ref imp(m.mk_implies(eq, a.mk_le(ji.m_start->get_owner(), a.mk_int(rational(jr.m_end, rational::ui64())))), m); + ctx.assert_expr(imp); + disj.push_back(eq); + } + // resource(j) = r1 || ... || resource(j) = r_n + expr_ref fml = mk_or(disj); + ctx.assert_expr(fml); + } + for (unsigned r = 0; r < m_resources.size(); ++r) { + vector& available = m_resources[r].m_available; + res_available::compare cmp; + std::sort(available.begin(), available.end(), cmp); + for (unsigned i = 0; i < available.size(); ++i) { + if (i + 1 < available.size() && + available[i].m_end > available[i + 1].m_start) { + throw default_exception("availability intervals should be disjoint"); + } + } + } + } + + /** + * check that each job is run on some resource according to + * requested capacity. + * + * Check that the sum of jobs run in each time instance + * does not exceed capacity. + */ + void theory_jobscheduler::validate_assignment() { + vector> start_times, end_times; + start_times.reserve(m_resources.size()); + end_times.reserve(m_resources.size()); + for (unsigned j = 0; j < m_jobs.size(); ++j) { + unsigned r = resource(j); + start_times[r].push_back(job_time(j, start(j))); + end_times[r].push_back(job_time(j, end(j))); + time_t cap = capacity_used(j, r, start(j), end(j)); + job_resource const& jr = get_job_resource(j, r); + if (jr.m_capacity > cap) { + throw default_exception("job not assigned full capacity"); + } + } + for (unsigned r = 0; r < m_resources.size(); ++r) { + unsigned load_pct = 0; + unsigned idx; + time_t next = 0, start = 0; + + // order jobs running on r by start, end-time intervals + // then consume ordered list to find jobs in scope. + vector& starts = start_times[r]; + vector& ends = end_times[r]; + job_time::compare cmp; + std::sort(starts.begin(), starts.end(), cmp); + std::sort(ends.begin(), ends.end(), cmp); + unsigned s_idx = 0, e_idx = 0; + + uint_set jobs; // set of jobs currently in scope. + while (resource_available(r, start, load_pct, next, idx)) { + if (load_pct == 0) { + start = next + 1; + continue; + } + // add jobs that begin at or before start. + while (s_idx < starts.size() && starts[s_idx].m_time <= start) { + jobs.insert(starts[s_idx].m_job); + ++s_idx; + } + // remove jobs that end before start. + while (e_idx < ends.size() && ends[s_idx].m_time < start) { + jobs.remove(ends[e_idx].m_job); + ++e_idx; + } + + // check that sum of job loads does not exceed 100% + unsigned cap = 0; + for (auto j : jobs) { + cap += get_job_resource(j, r).m_loadpct; + } + if (cap > 100) { + throw default_exception("capacity on resource exceeded"); + } + if (s_idx < starts.size()) { + // start time of the next unprocessed job. + start = starts[s_idx].m_time; + } + else { + // done checking. + break; + } + } + } + } + + theory_jobscheduler::job_resource const& theory_jobscheduler::get_job_resource(unsigned j, unsigned r) const { + job_info const& ji = m_jobs[j]; + return ji.m_resources[ji.m_resource2index[r]]; + } + + bool theory_jobscheduler::resource_available(unsigned r, time_t t, unsigned& load_pct, time_t& end, unsigned& idx) { + vector& available = m_resources[r].m_available; + unsigned lo = 0, hi = available.size(), mid = hi / 2; + while (lo < hi) { + res_available const& ra = available[mid]; + if (ra.m_start <= t && t <= ra.m_end) { + end = ra.m_end; + load_pct = ra.m_loadpct; + idx = mid; + return true; + } + else if (ra.m_start > t && mid > 0) { + hi = mid - 1; + mid = lo + (mid - lo) / 2; + } + else if (ra.m_end < t) { + lo = mid + 1; + mid += (hi - mid) / 2; + } + else { + break; + } + } + return false; + } + + /** + * compute the capacity used by job j on resource r between start and end. + * The resource r partitions time intervals into segments where a fraction of + * the full capacity of the resource is available. The resource can use up to the + * available fraction. + */ + time_t theory_jobscheduler::capacity_used(unsigned j, unsigned r, time_t start, time_t end) { + time_t cap = 0; + unsigned j_load_pct = get_job_resource(j, r).m_loadpct; + vector& available = m_resources[r].m_available; + unsigned load_pct = 0; + time_t next = 0; + unsigned idx = 0; + if (!resource_available(r, start, load_pct, next, idx)) { + return cap; + } + while (start < end) { + next = std::min(end, next); + SASSERT(start < next); + cap += (std::min(j_load_pct, load_pct) / j_load_pct) * (next - start - 1); + ++idx; + if (idx == available.size()) { + break; + } + start = available[idx].m_start; + next = available[idx].m_end; + load_pct = available[idx].m_loadpct; + } + return cap; + } }; diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index ec09ce0ce..f10b72d81 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -22,20 +22,35 @@ Revision History: #include "smt/smt_theory.h" #include "ast/jobshop_decl_plugin.h" +#include "ast/arith_decl_plugin.h" namespace smt { + typedef uint64_t time_t; + class theory_jobscheduler : public theory { struct job_resource { unsigned m_resource_id; // id of resource unsigned m_capacity; // amount of resource to use unsigned m_loadpct; // assuming loadpct - uint64_t m_end; // must run before - job_resource(unsigned r, unsigned cap, unsigned loadpct, uint64_t end): + time_t m_end; // must run before + job_resource(unsigned r, unsigned cap, unsigned loadpct, time_t end): m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_end(end) {} }; + struct job_time { + unsigned m_job; + time_t m_time; + job_time(unsigned j, time_t time): m_job(j), m_time(time) {} + + struct compare { + bool operator()(job_time const& jt1, job_time const& jt2) const { + return jt1.m_time < jt2.m_time; + } + }; + }; + struct job_info { vector m_resources; // resources allowed to run job. u_map m_resource2index; // resource to index into vector @@ -47,24 +62,31 @@ namespace smt { struct res_available { unsigned m_loadpct; - uint64_t m_start; - uint64_t m_end; - res_available(unsigned load_pct, uint64_t start, uint64_t end): + time_t m_start; + time_t m_end; + res_available(unsigned load_pct, time_t start, time_t end): m_loadpct(load_pct), m_start(start), m_end(end) {} + struct compare { + bool operator()(res_available const& ra1, res_available const& ra2) const { + return ra1.m_start < ra2.m_start; + } + }; + }; struct res_info { unsigned_vector m_jobs; // jobs allocated to run on resource vector m_available; // time intervals where resource is available - uint64_t m_end; // can't run after - res_info(): m_end(std::numeric_limits::max()) {} + time_t m_end; // can't run after + res_info(): m_end(std::numeric_limits::max()) {} }; ast_manager& m; jobshop_util u; + arith_util a; unsigned_vector m_var2index; vector m_jobs; vector m_resources; @@ -113,17 +135,31 @@ namespace smt { public: // assignments: - uint64_t est(unsigned j); // earliest start time of job j - uint64_t lst(unsigned j); // last start time - uint64_t ect(unsigned j); // earliest completion time - uint64_t lct(unsigned j); // last completion time - uint64_t start(unsigned j); // start time of job j - uint64_t end(unsigned j); // end time of job j + time_t est(unsigned j); // earliest start time of job j + time_t lst(unsigned j); // last start time + time_t ect(unsigned j); // earliest completion time + time_t lct(unsigned j); // last completion time + time_t start(unsigned j); // start time of job j + time_t end(unsigned j); // end time of job j unsigned resource(unsigned j); // resource of job j // set up model - void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, uint64_t end); - void add_resource_available(unsigned r, unsigned max_loadpct, uint64_t start, uint64_t end); + void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end); + void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end); + void add_done(); + + // validate assignment + void validate_assignment(); + bool resource_available(unsigned r, time_t t, unsigned& load_pct, time_t& end, unsigned& idx); // load available on resource r at time t. + time_t capacity_used(unsigned j, unsigned r, time_t start, time_t end); // capacity used between start and end + + job_resource const& get_job_resource(unsigned j, unsigned r) const; + + std::ostream& display(std::ostream & out, res_info const& r) const; + std::ostream& display(std::ostream & out, res_available const& r) const; + std::ostream& display(std::ostream & out, job_info const& r) const; + std::ostream& display(std::ostream & out, job_resource const& r) const; + }; }; From abd902d58c8144da339c3700f407a4cc7f04cf79 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 11 Aug 2018 18:14:32 -0700 Subject: [PATCH 15/65] n/a Signed-off-by: Nikolaj Bjorner --- src/smt/theory_jobscheduler.cpp | 396 ++++++++++++++++++++++++++------ src/smt/theory_jobscheduler.h | 58 ++++- 2 files changed, 372 insertions(+), 82 deletions(-) diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index d2cab8bf8..f12193c2e 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -78,6 +78,18 @@ namespace smt { } final_check_status theory_jobscheduler::final_check_eh() { + + // ensure that each job starts within an avaialable interval. + // ! (end_previous_interval < start(j) < start_of_next_interval) + + bool blocked = false; + for (unsigned r = 0; r < m_resources.size(); ++r) { + if (constrain_resource_energy(r)) { + blocked = true; + } + } + + if (blocked) return FC_CONTINUE; return FC_DONE; } @@ -85,13 +97,199 @@ namespace smt { return false; } - void theory_jobscheduler::propagate() { + literal theory_jobscheduler::mk_literal(expr * e) { + expr_ref _e(e, m); + context& ctx = get_context(); + if (!ctx.e_internalized(e)) { + ctx.internalize(e, false); + } + ctx.mark_as_relevant(ctx.get_enode(e)); + return ctx.get_literal(e); + } + + literal theory_jobscheduler::mk_ge_lit(expr* e, time_t t) { + return mk_literal(mk_ge(e, t)); + } + + expr* theory_jobscheduler::mk_ge(expr* e, time_t t) { + return a.mk_ge(e, a.mk_int(rational(t, rational::ui64()))); + } + + expr* theory_jobscheduler::mk_ge(enode* e, time_t t) { + return mk_ge(e->get_owner(), t); + } + + literal theory_jobscheduler::mk_le_lit(expr* e, time_t t) { + return mk_literal(mk_le(e, t)); + } + + expr* theory_jobscheduler::mk_le(expr* e, time_t t) { + return a.mk_le(e, a.mk_int(rational(t, rational::ui64()))); + } + + literal theory_jobscheduler::mk_le(enode* l, enode* r) { + context& ctx = get_context(); + expr_ref le(a.mk_le(l->get_owner(), r->get_owner()), m); + ctx.get_rewriter()(le); + return mk_literal(le); + } + + expr* theory_jobscheduler::mk_le(enode* e, time_t t) { + return mk_le(e->get_owner(), t); + } + + /** + * iterator of job overlaps. + */ + theory_jobscheduler::job_overlap::job_overlap(vector& starts, vector& ends): + m_starts(starts), m_ends(ends), s_idx(0), e_idx(0) { + job_time::compare cmp; + std::sort(starts.begin(), starts.end(), cmp); + std::sort(ends.begin(), ends.end(), cmp); + } + + bool theory_jobscheduler::job_overlap::next(time_t& start) { + if (s_idx == m_starts.size()) { + return false; + } + while (s_idx < m_starts.size() && m_starts[s_idx].m_time <= start) { + m_jobs.insert(m_starts[s_idx].m_job); + ++s_idx; + } + // remove jobs that end before start. + while (e_idx < m_ends.size() && m_ends[s_idx].m_time < start) { + m_jobs.remove(m_ends[e_idx].m_job); + ++e_idx; + } + // TBD: check logic + if (s_idx < m_starts.size()) { + start = m_starts[s_idx].m_time; + } + return true; + } + + + /** + * r = resource(j) & start(j) >= slb => end(j) >= ect(j, r, slb) + */ + void theory_jobscheduler::propagate_end_time(unsigned j, unsigned r) { + time_t slb = est(j); + time_t clb = ect(j, r, slb); + context& ctx = get_context(); + + if (clb > end(j)) { + job_info const& ji = m_jobs[j]; + literal start_ge_lo = mk_literal(mk_ge(ji.m_start, slb)); + if (ctx.get_assignment(start_ge_lo) != l_true) { + return; + } + enode_pair eq(ji.m_resource, ctx.get_enode(u.mk_resource(r))); + if (eq.first->get_root() != eq.second->get_root()) { + return; + } + + literal end_ge_lo = mk_literal(mk_ge(ji.m_end, clb)); + // Initialization ensures that satisfiable states have completion time below end. + VERIFY(clb <= get_job_resource(j, r).m_end); + region& r = ctx.get_region(); + ctx.assign(end_ge_lo, + ctx.mk_justification( + ext_theory_propagation_justification(get_id(), r, 1, &start_ge_lo, 1, &eq, end_ge_lo, 0, nullptr))); + } + } + + /** + * For time interval [t0, t1] the end-time can be computed as a function + * of start time based on reource load availability. + * + * r = resource(j) & t1 >= start(j) >= t0 => end(j) = start(j) + ect(j, r, t0) - t0 + */ + void theory_jobscheduler::propagate_end_time_interval(unsigned j, unsigned r) { + // TBD + // establish maximal intervals around start, such that end time is a linear function of start. + } + + void theory_jobscheduler::propagate_resource_energy(unsigned r) { } + + /** + * Ensure that job overlaps don't exceed available energy + */ + bool theory_jobscheduler::constrain_resource_energy(unsigned r) { + bool blocked = false; + vector starts, ends; + res_info const& ri = m_resources[r]; + for (unsigned j : ri.m_jobs) { + if (resource(j) == r) { + starts.push_back(job_time(j, start(j))); + ends.push_back(job_time(j, end(j))); + } + } + job_overlap overlap(starts, ends); + time_t start = 0; + while (overlap.next(start)) { + unsigned cap = 0; + auto const& jobs = overlap.jobs(); + for (auto j : jobs) { + cap += get_job_resource(j, r).m_loadpct; + if (cap > 100) { + block_job_overlap(r, jobs, j); + blocked = true; + goto try_next_overlap; + } + } + try_next_overlap: + ; + } + return blocked; + } + + void theory_jobscheduler::block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job) { + // + // block the following case: + // each job is assigned to r. + // max { start(j) | j0..last_job } <= min { end(j) | j0..last_job } + // joint capacity of jobs exceeds availability of resource. + // + time_t max_start = 0; + unsigned max_j = last_job; + for (auto j : jobs) { + if (max_start < start(j)) { + max_start = start(j); + max_j = j; + } + if (j == last_job) break; + } + literal_vector lits; + for (auto j : jobs) { + // create literals for: + // resource(j) == r + // m_jobs[j].m_start <= m_jobs[max_j].m_start; + // m_jobs[max_j].m_start <= m_jobs[j].m_end; + lits.push_back(~mk_eq(u.mk_job2resource(j), u.mk_resource(r), false)); + lits.push_back(~mk_le(m_jobs[j].m_start, m_jobs[max_j].m_start)); + lits.push_back(~mk_le(m_jobs[max_j].m_start, m_jobs[max_j].m_end)); + if (j == last_job) break; + } + context& ctx = get_context(); + ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_AUX_LEMMA, nullptr); + } + + void theory_jobscheduler::propagate() { + for (unsigned j = 0; j < m_jobs.size(); ++j) { + job_info const& ji = m_jobs[j]; + unsigned r = resource(j); + propagate_end_time(j, r); + propagate_end_time_interval(j, r); + } + for (unsigned r = 0; r < m_resources.size(); ++r) { + // TBD: check energy constraints on resources. + } + } theory_jobscheduler::theory_jobscheduler(ast_manager& m): theory(m.get_family_id("jobshop")), m(m), u(m), a(m) { - } std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { @@ -147,30 +345,37 @@ namespace smt { } time_t theory_jobscheduler::est(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } time_t theory_jobscheduler::lst(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } time_t theory_jobscheduler::ect(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } time_t theory_jobscheduler::lct(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } time_t theory_jobscheduler::start(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } time_t theory_jobscheduler::end(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } unsigned theory_jobscheduler::resource(unsigned j) { + NOT_IMPLEMENTED_YET(); return 0; } @@ -191,7 +396,7 @@ namespace smt { void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end) { SASSERT(get_context().at_base_level()); - SASSERT(start < end); + SASSERT(start <= end); m_resources.reserve(r + 1); m_resources[r].m_available.push_back(res_available(max_loadpct, start, end)); } @@ -215,16 +420,27 @@ namespace smt { throw default_exception("every job should be associated with at least one resource"); } // resource(j) = r => end(j) <= end(j, r) + // resource(j) = r => start(j) <= lst(j, r, end(j, r)) for (job_resource const& jr : ji.m_resources) { - app_ref res(u.mk_resource(jr.m_resource_id), m); + unsigned r = jr.m_resource_id; + app_ref res(u.mk_resource(r), m); expr_ref eq(m.mk_eq(job, res), m); - expr_ref imp(m.mk_implies(eq, a.mk_le(ji.m_start->get_owner(), a.mk_int(rational(jr.m_end, rational::ui64())))), m); - ctx.assert_expr(imp); + expr_ref imp(m.mk_implies(eq, mk_le(ji.m_end, jr.m_end)), m); + ctx.assert_expr(imp); + imp = m.mk_implies(eq, mk_le(ji.m_start, lst(j, r))); + ctx.assert_expr(imp); disj.push_back(eq); } // resource(j) = r1 || ... || resource(j) = r_n expr_ref fml = mk_or(disj); ctx.assert_expr(fml); + + // start(j) >= 0 + fml = mk_ge(ji.m_start, 0); + ctx.assert_expr(fml); + + // end(j) <= time_t::max + // is implied by resource(j) = r => end(j) <= end(j, r) } for (unsigned r = 0; r < m_resources.size(); ++r) { vector& available = m_resources[r].m_available; @@ -254,59 +470,24 @@ namespace smt { unsigned r = resource(j); start_times[r].push_back(job_time(j, start(j))); end_times[r].push_back(job_time(j, end(j))); - time_t cap = capacity_used(j, r, start(j), end(j)); - job_resource const& jr = get_job_resource(j, r); - if (jr.m_capacity > cap) { + if (ect(j, r, start(j)) > end(j)) { throw default_exception("job not assigned full capacity"); } } for (unsigned r = 0; r < m_resources.size(); ++r) { - unsigned load_pct = 0; - unsigned idx; - time_t next = 0, start = 0; - // order jobs running on r by start, end-time intervals // then consume ordered list to find jobs in scope. - vector& starts = start_times[r]; - vector& ends = end_times[r]; - job_time::compare cmp; - std::sort(starts.begin(), starts.end(), cmp); - std::sort(ends.begin(), ends.end(), cmp); - unsigned s_idx = 0, e_idx = 0; - - uint_set jobs; // set of jobs currently in scope. - while (resource_available(r, start, load_pct, next, idx)) { - if (load_pct == 0) { - start = next + 1; - continue; - } - // add jobs that begin at or before start. - while (s_idx < starts.size() && starts[s_idx].m_time <= start) { - jobs.insert(starts[s_idx].m_job); - ++s_idx; - } - // remove jobs that end before start. - while (e_idx < ends.size() && ends[s_idx].m_time < start) { - jobs.remove(ends[e_idx].m_job); - ++e_idx; - } - + time_t start = 0; + job_overlap overlap(start_times[r], end_times[r]); + while (overlap.next(start)) { // check that sum of job loads does not exceed 100% unsigned cap = 0; - for (auto j : jobs) { + for (auto j : overlap.jobs()) { cap += get_job_resource(j, r).m_loadpct; } if (cap > 100) { throw default_exception("capacity on resource exceeded"); } - if (s_idx < starts.size()) { - // start time of the next unprocessed job. - start = starts[s_idx].m_time; - } - else { - // done checking. - break; - } } } } @@ -316,14 +497,12 @@ namespace smt { return ji.m_resources[ji.m_resource2index[r]]; } - bool theory_jobscheduler::resource_available(unsigned r, time_t t, unsigned& load_pct, time_t& end, unsigned& idx) { + bool theory_jobscheduler::resource_available(unsigned r, time_t t, unsigned& idx) { vector& available = m_resources[r].m_available; unsigned lo = 0, hi = available.size(), mid = hi / 2; while (lo < hi) { res_available const& ra = available[mid]; if (ra.m_start <= t && t <= ra.m_end) { - end = ra.m_end; - load_pct = ra.m_loadpct; idx = mid; return true; } @@ -342,35 +521,108 @@ namespace smt { return false; } + /** - * compute the capacity used by job j on resource r between start and end. - * The resource r partitions time intervals into segments where a fraction of - * the full capacity of the resource is available. The resource can use up to the - * available fraction. - */ - time_t theory_jobscheduler::capacity_used(unsigned j, unsigned r, time_t start, time_t end) { - time_t cap = 0; - unsigned j_load_pct = get_job_resource(j, r).m_loadpct; + * compute earliest completion time for job j on resource r starting at time start. + */ + time_t theory_jobscheduler::ect(unsigned j, unsigned r, time_t start) { + job_resource const& jr = get_job_resource(j, r); vector& available = m_resources[r].m_available; - unsigned load_pct = 0; - time_t next = 0; + + unsigned j_load_pct = jr.m_loadpct; + time_t cap = jr.m_capacity; unsigned idx = 0; - if (!resource_available(r, start, load_pct, next, idx)) { - return cap; + if (!resource_available(r, start, idx)) { + return std::numeric_limits::max(); } - while (start < end) { - next = std::min(end, next); - SASSERT(start < next); - cap += (std::min(j_load_pct, load_pct) / j_load_pct) * (next - start - 1); - ++idx; - if (idx == available.size()) { - break; + SASSERT(cap > 0); + + for (; idx < available.size(); ++idx) { + start = std::max(start, available[idx].m_start); + time_t end = available[idx].m_end; + unsigned load_pct = available[idx].m_loadpct; + time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); + if (delta > cap) { + // + // solve for end: + // cap = load * (end - start + 1) + // <=> + // cap / load = (end - start + 1) + // <=> + // end = cap / load + start - 1 + // + end = solve_for_end(load_pct, j_load_pct, start, cap); + cap = 0; + } + else { + cap -= delta; + } + if (cap == 0) { + return end; } - start = available[idx].m_start; - next = available[idx].m_end; - load_pct = available[idx].m_loadpct; } - return cap; + return std::numeric_limits::max(); + } + + time_t theory_jobscheduler::solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap) { + SASSERT(load_pct > 0); + SASSERT(job_load_pct > 0); + // cap = (load / job_load_pct) * (start - end + 1) + // <=> + // start - end + 1 = (cap * job_load_pct) / load + // <=> + // end = start + 1 - (cap * job_load_pct) / load + // <=> + // end = (load * (start + 1) - cap * job_load_pct) / load + unsigned load = std::min(load_pct, job_load_pct); + return (load * (start + 1) - cap * job_load_pct) / load; + } + + time_t theory_jobscheduler::solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap) { + SASSERT(load_pct > 0); + SASSERT(job_load_pct > 0); + // cap = (load / job_load_pct) * (start - end + 1) + // <=> + // start - end + 1 = (cap * job_load_pct) / load + // <=> + // start = (cap * job_load_pct) / load + end - 1 + // <=> + // start = (load * (end - 1) + cap * job_load_pct) / load + unsigned load = std::min(load_pct, job_load_pct); + return (load * (end - 1) + cap * job_load_pct) / load; + } + + time_t theory_jobscheduler::solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end) { + SASSERT(job_load_pct > 0); + unsigned load = std::min(load_pct, job_load_pct); + return (load * (end - start + 1)) / job_load_pct; + } + + /** + * Compute last start time for job on resource r. + */ + time_t theory_jobscheduler::lst(unsigned j, unsigned r) { + job_resource const& jr = get_job_resource(j, r); + vector& available = m_resources[r].m_available; + unsigned j_load_pct = jr.m_loadpct; + time_t cap = jr.m_capacity; + for (unsigned idx = available.size(); idx-- > 0; ) { + time_t start = available[idx].m_start; + time_t end = available[idx].m_end; + unsigned load_pct = available[idx].m_loadpct; + time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); + if (delta > cap) { + start = solve_for_start(load_pct, j_load_pct, start, cap); + cap = 0; + } + else { + cap -= delta; + } + if (cap == 0) { + return start; + } + } + throw default_exception("there is insufficient capacity on the resource to run the job"); } }; diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index f10b72d81..369cc1955 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -20,9 +20,10 @@ Revision History: --*/ #pragma once; -#include "smt/smt_theory.h" +#include "util/uint_set.h" #include "ast/jobshop_decl_plugin.h" #include "ast/arith_decl_plugin.h" +#include "smt/smt_theory.h" namespace smt { @@ -74,13 +75,12 @@ namespace smt { return ra1.m_start < ra2.m_start; } }; - }; struct res_info { unsigned_vector m_jobs; // jobs allocated to run on resource vector m_available; // time intervals where resource is available - time_t m_end; // can't run after + time_t m_end; // can't run after res_info(): m_end(std::numeric_limits::max()) {} }; @@ -131,10 +131,15 @@ namespace smt { bool get_value(enode * n, expr_ref & r) override; - theory * mk_fresh(context * new_ctx) override; // { return alloc(theory_jobscheduler, new_ctx->get_manager()); } + theory * mk_fresh(context * new_ctx) override; public: - // assignments: + // set up job/resource global constraints + void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end); + void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end); + void add_done(); + + // assignments time_t est(unsigned j); // earliest start time of job j time_t lst(unsigned j); // last start time time_t ect(unsigned j); // earliest completion time @@ -142,19 +147,52 @@ namespace smt { time_t start(unsigned j); // start time of job j time_t end(unsigned j); // end time of job j unsigned resource(unsigned j); // resource of job j + + // derived bounds + time_t ect(unsigned j, unsigned r, time_t start); + time_t lst(unsigned j, unsigned r); - // set up model - void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end); - void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end); - void add_done(); + time_t solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap); + time_t solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap); + time_t solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end); // validate assignment void validate_assignment(); - bool resource_available(unsigned r, time_t t, unsigned& load_pct, time_t& end, unsigned& idx); // load available on resource r at time t. + bool resource_available(unsigned r, time_t t, unsigned& idx); // load available on resource r at time t. time_t capacity_used(unsigned j, unsigned r, time_t start, time_t end); // capacity used between start and end job_resource const& get_job_resource(unsigned j, unsigned r) const; + // propagation + void propagate_end_time(unsigned j, unsigned r); + void propagate_end_time_interval(unsigned j, unsigned r); + void propagate_resource_energy(unsigned r); + + // final check constraints + bool constrain_resource_energy(unsigned r); + + void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job); + + class job_overlap { + vector & m_starts, &m_ends; + unsigned s_idx, e_idx; // index into starts/ends + uint_set m_jobs; + public: + job_overlap(vector& starts, vector& ends); + bool next(time_t& start); + uint_set const& jobs() const { return m_jobs; } + }; + + // term builders + literal mk_ge_lit(expr* e, time_t t); + expr* mk_ge(expr* e, time_t t); + expr* mk_ge(enode* e, time_t t); + literal mk_le_lit(expr* e, time_t t); + expr* mk_le(expr* e, time_t t); + expr* mk_le(enode* e, time_t t); + literal mk_le(enode* l, enode* r); + literal mk_literal(expr* e); + std::ostream& display(std::ostream & out, res_info const& r) const; std::ostream& display(std::ostream & out, res_available const& r) const; std::ostream& display(std::ostream & out, job_info const& r) const; From 0af00e62dee673c8025bf7a7a5a7e865f1192f12 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 12 Aug 2018 12:42:26 -0700 Subject: [PATCH 16/65] abstract arithmetic value extraction Signed-off-by: Nikolaj Bjorner --- src/smt/CMakeLists.txt | 1 + src/smt/smt_arith_value.cpp | 113 ++++++++++++++++ src/smt/smt_arith_value.h | 37 ++++++ src/smt/smt_context.h | 5 + src/smt/theory_jobscheduler.cpp | 224 +++++++++++++++++++++++++++----- src/smt/theory_jobscheduler.h | 34 +++-- src/smt/theory_lra.cpp | 45 +++++-- src/smt/theory_lra.h | 3 + 8 files changed, 405 insertions(+), 57 deletions(-) create mode 100644 src/smt/smt_arith_value.cpp create mode 100644 src/smt/smt_arith_value.h diff --git a/src/smt/CMakeLists.txt b/src/smt/CMakeLists.txt index 13a00d44e..4b640f8ca 100644 --- a/src/smt/CMakeLists.txt +++ b/src/smt/CMakeLists.txt @@ -13,6 +13,7 @@ z3_add_component(smt old_interval.cpp qi_queue.cpp smt_almost_cg_table.cpp + smt_arith_value.cpp smt_case_split_queue.cpp smt_cg_table.cpp smt_checker.cpp diff --git a/src/smt/smt_arith_value.cpp b/src/smt/smt_arith_value.cpp new file mode 100644 index 000000000..ecf924111 --- /dev/null +++ b/src/smt/smt_arith_value.cpp @@ -0,0 +1,113 @@ + +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + smt_arith_value.cpp + +Abstract: + + Utility to extract arithmetic values from context. + +Author: + + Nikolaj Bjorner (nbjorner) 2018-12-08. + +Revision History: + +--*/ +#pragma once; + +#include "smt/smt_arith_value.h" +#include "smt/theory_lra.h" +#include "smt/theory_arith.h" + +namespace smt { + + arith_value::arith_value(context& ctx): + m_ctx(ctx), m(ctx.get_manager()), a(m) {} + + bool arith_value::get_lo(expr* e, rational& lo, bool& is_strict) { + if (!m_ctx.e_internalized(e)) return false; + expr_ref _lo(m); + family_id afid = a.get_family_id(); + is_strict = false; + enode* next = m_ctx.get_enode(e), *n = next; + bool found = false; + bool is_strict1; + rational lo1; + theory* th = m_ctx.get_theory(afid); + theory_mi_arith* tha = dynamic_cast(th); + theory_i_arith* thi = dynamic_cast(th); + theory_lra* thr = dynamic_cast(th); + do { + if (tha && tha->get_lower(next, _lo) && a.is_numeral(_lo, lo1)) { + if (!found || lo1 > lo) lo = lo1; + found = true; + } + else if (thi && thi->get_lower(next, _lo) && a.is_numeral(_lo, lo1)) { + if (!found || lo1 > lo) lo = lo1; + found = true; + } + else if (thr && thr->get_lower(next, lo1, is_strict1)) { + if (!found || lo1 > lo || (lo == lo1 && is_strict1)) lo = lo1, is_strict = is_strict1; + found = true; + } + next = next->get_next(); + } + while (n != next); + return found; + } + + bool arith_value::get_up(expr* e, rational& up, bool& is_strict) { + if (!m_ctx.e_internalized(e)) return false; + expr_ref _up(m); + family_id afid = a.get_family_id(); + is_strict = false; + enode* next = m_ctx.get_enode(e), *n = next; + bool found = false, is_strict1; + rational up1; + theory* th = m_ctx.get_theory(afid); + theory_mi_arith* tha = dynamic_cast(th); + theory_i_arith* thi = dynamic_cast(th); + theory_lra* thr = dynamic_cast(th); + do { + if (tha && tha->get_upper(next, _up) && a.is_numeral(_up, up1)) { + if (!found || up1 < up) up = up1; + found = true; + } + else if (thi && thi->get_upper(next, _up) && a.is_numeral(_up, up1)) { + if (!found || up1 < up) up = up1; + found = true; + } + else if (thr && thr->get_upper(next, up1, is_strict1)) { + if (!found || up1 < up || (up1 == up && is_strict1)) up = up1, is_strict = is_strict1; + found = true; + } + next = next->get_next(); + } + while (n != next); + return found; + } + + bool arith_value::get_value(expr* e, rational& val) { + if (!m_ctx.e_internalized(e)) return false; + expr_ref _val(m); + enode* next = m_ctx.get_enode(e), *n = next; + family_id afid = a.get_family_id(); + theory* th = m_ctx.get_theory(afid); + theory_mi_arith* tha = dynamic_cast(th); + theory_i_arith* thi = dynamic_cast(th); + theory_lra* thr = dynamic_cast(th); + do { + e = next->get_owner(); + if (tha && tha->get_value(next, _val) && a.is_numeral(_val, val)) return true; + if (thi && thi->get_value(next, _val) && a.is_numeral(_val, val)) return true; + if (thr && thr->get_value(next, val)) return true; + next = next->get_next(); + } + while (next != n); + return false; + } +}; diff --git a/src/smt/smt_arith_value.h b/src/smt/smt_arith_value.h new file mode 100644 index 000000000..9b0f833ac --- /dev/null +++ b/src/smt/smt_arith_value.h @@ -0,0 +1,37 @@ + +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + smt_arith_value.h + +Abstract: + + Utility to extract arithmetic values from context. + +Author: + + Nikolaj Bjorner (nbjorner) 2018-12-08. + +Revision History: + +--*/ +#pragma once; + +#include "ast/arith_decl_plugin.h" +#include "smt/smt_context.h" + + +namespace smt { + class arith_value { + context& m_ctx; + ast_manager& m; + arith_util a; + public: + arith_value(context& ctx); + bool get_lo(expr* e, rational& lo, bool& strict); + bool get_up(expr* e, rational& up, bool& strict); + bool get_value(expr* e, rational& value); + }; +}; diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index ff92f6f95..f545f7c6d 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1243,6 +1243,11 @@ namespace smt { public: bool can_propagate() const; + // Retrieve arithmetic values. + bool get_arith_lo(expr* e, rational& lo, bool& strict); + bool get_arith_up(expr* e, rational& up, bool& strict); + bool get_arith_value(expr* e, rational& value); + // ----------------------------------- // // Model checking... (must be improved) diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index f12193c2e..173d44c52 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -14,10 +14,39 @@ Author: Revision History: +TODO: + +- arithmetic interface + +- propagation queue: + - register theory variables for catching when jobs are bound to resources + - register bounds on start times to propagate energy constraints + - more general registration mechanism for arithmetic theory. +- csp_cmds + - use predicates for add_ feature set? Closed world. + set up environment in one swoop. + - interact with opt +- jobs without resources + - complain or add dummy resource? At which level. + +Features: +- properties +- priority + - try mss based from outside +- job-goal + - try optimization based on arithmetic solver. + - earliest start, latest start +- constraint level +- resource groups + - resource groups like a resource + - resources bound to resource groups within time intervals + - job can require to use K resources from a resource group simultaneously. + --*/ #include "smt/theory_jobscheduler.h" #include "smt/smt_context.h" +#include "smt/smt_arith_value.h" namespace smt { @@ -79,16 +108,18 @@ namespace smt { final_check_status theory_jobscheduler::final_check_eh() { - // ensure that each job starts within an avaialable interval. - // ! (end_previous_interval < start(j) < start_of_next_interval) - bool blocked = false; for (unsigned r = 0; r < m_resources.size(); ++r) { if (constrain_resource_energy(r)) { blocked = true; } } - + for (unsigned j = 0; j < m_jobs.size(); ++j) { + if (constrain_end_time_interval(j, resource(j))) { + blocked = true; + } + } + if (blocked) return FC_CONTINUE; return FC_DONE; } @@ -204,9 +235,56 @@ namespace smt { * * r = resource(j) & t1 >= start(j) >= t0 => end(j) = start(j) + ect(j, r, t0) - t0 */ - void theory_jobscheduler::propagate_end_time_interval(unsigned j, unsigned r) { - // TBD - // establish maximal intervals around start, such that end time is a linear function of start. + bool theory_jobscheduler::constrain_end_time_interval(unsigned j, unsigned r) { + unsigned idx1 = 0, idx2 = 0; + time_t s = start(j); + if (!resource_available(r, s, idx1)) return false; + vector& available = m_resources[r].m_available; + time_t e = ect(j, r, s); + if (!resource_available(r, e, idx2)) return false; + time_t start1 = available[idx1].m_start; + time_t end1 = available[idx1].m_end; + unsigned cap1 = available[idx1].m_loadpct; + time_t start2 = available[idx2].m_start; + time_t end2 = available[idx2].m_end; + unsigned cap2 = available[idx2].m_loadpct; + // calculate minimal start1 <= t0 <= s, such that ect(j, r, t0) >= start2 + // calculate maximal s <= t1 <= end1, such that ect(j, r, t1) <= end2 + time_t delta1 = (s - start1)*cap1; + time_t delta2 = (e - start2)*cap2; + time_t t0, t1; + if (delta1 <= delta2) { + t0 = start1; + } + else { + // solve for t0: + // (s - t0)*cap1 = (e - start2)*cap2; + t0 = s - (delta2 / cap1); + } + delta1 = (end1 - s)*cap1; + delta2 = (end2 - e)*cap2; + if (delta1 <= delta2) { + t1 = end1; + } + else { + // solve for t1: + // (t1 - s)*cap1 = (end2 - e)*cap2 + t1 = s + (delta2 / cap1); + } + + time_t delta = ect(j, r, t0) - t0; + if (end(j) == start(j) + delta) { + return false; + } + literal_vector lits; + lits.push_back(~mk_eq(u.mk_job2resource(j), u.mk_resource(r), false)); + lits.push_back(~mk_ge_lit(u.mk_start(j), t0)); + lits.push_back(~mk_le_lit(u.mk_start(j), t1)); + expr_ref rhs(a.mk_add(u.mk_start(j), a.mk_int(rational(delta, rational::ui64()))), m); + lits.push_back(mk_eq(u.mk_end(j), rhs, false)); + context& ctx = get_context(); + ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_AUX_LEMMA, nullptr); + return true; } void theory_jobscheduler::propagate_resource_energy(unsigned r) { @@ -268,8 +346,10 @@ namespace smt { // m_jobs[j].m_start <= m_jobs[max_j].m_start; // m_jobs[max_j].m_start <= m_jobs[j].m_end; lits.push_back(~mk_eq(u.mk_job2resource(j), u.mk_resource(r), false)); - lits.push_back(~mk_le(m_jobs[j].m_start, m_jobs[max_j].m_start)); - lits.push_back(~mk_le(m_jobs[max_j].m_start, m_jobs[max_j].m_end)); + if (j != max_j) { + lits.push_back(~mk_le(m_jobs[j].m_start, m_jobs[max_j].m_start)); + lits.push_back(~mk_le(m_jobs[max_j].m_start, m_jobs[j].m_end)); + } if (j == last_job) break; } context& ctx = get_context(); @@ -281,7 +361,6 @@ namespace smt { job_info const& ji = m_jobs[j]; unsigned r = resource(j); propagate_end_time(j, r); - propagate_end_time_interval(j, r); } for (unsigned r = 0; r < m_resources.size(); ++r) { // TBD: check energy constraints on resources. @@ -345,13 +424,23 @@ namespace smt { } time_t theory_jobscheduler::est(unsigned j) { - NOT_IMPLEMENTED_YET(); + arith_value av(get_context()); + rational val; + bool is_strict; + if (av.get_lo(u.mk_start(j), val, is_strict) && !is_strict && val.is_uint64()) { + return val.get_uint64(); + } return 0; } time_t theory_jobscheduler::lst(unsigned j) { - NOT_IMPLEMENTED_YET(); - return 0; + arith_value av(get_context()); + rational val; + bool is_strict; + if (av.get_up(u.mk_start(j), val, is_strict) && !is_strict && val.is_uint64()) { + return val.get_uint64(); + } + return std::numeric_limits::max(); } time_t theory_jobscheduler::ect(unsigned j) { @@ -379,9 +468,15 @@ namespace smt { return 0; } + void theory_jobscheduler::set_preemptable(unsigned j, bool is_preemptable) { + m_jobs.reserve(j + 1); + m_jobs[j].m_is_preemptable = is_preemptable; + } - void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end) { + void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end, properties const& ps) { SASSERT(get_context().at_base_level()); + SASSERT(1 <= loadpct && loadpct <= 100); + SASSERT(0 < cap); m_jobs.reserve(j + 1); m_resources.reserve(r + 1); job_info& ji = m_jobs[j]; @@ -389,16 +484,17 @@ namespace smt { throw default_exception("resource already bound to job"); } ji.m_resource2index.insert(r, ji.m_resources.size()); - ji.m_resources.push_back(job_resource(r, cap, loadpct, end)); + ji.m_resources.push_back(job_resource(r, cap, loadpct, end, ps)); SASSERT(!m_resources[r].m_jobs.contains(j)); m_resources[r].m_jobs.push_back(j); } - void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end) { + void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps) { SASSERT(get_context().at_base_level()); + SASSERT(1 <= max_loadpct && max_loadpct <= 100); SASSERT(start <= end); m_resources.reserve(r + 1); - m_resources[r].m_available.push_back(res_available(max_loadpct, start, end)); + m_resources[r].m_available.push_back(res_available(max_loadpct, start, end, ps)); } /* @@ -411,7 +507,18 @@ namespace smt { * Ensure that the availability slots for each resource is sorted by time. */ void theory_jobscheduler::add_done() { - context & ctx = get_context(); + context & ctx = get_context(); + + // sort availability intervals + for (unsigned r = 0; r < m_resources.size(); ++r) { + res_info& ri = m_resources[r]; + vector& available = ri.m_available; + res_available::compare cmp; + std::sort(available.begin(), available.end(), cmp); + } + + expr_ref fml(m); + for (unsigned j = 0; j < m_jobs.size(); ++j) { job_info const& ji = m_jobs[j]; expr_ref_vector disj(m); @@ -419,38 +526,83 @@ namespace smt { if (ji.m_resources.empty()) { throw default_exception("every job should be associated with at least one resource"); } - // resource(j) = r => end(j) <= end(j, r) - // resource(j) = r => start(j) <= lst(j, r, end(j, r)) + + // start(j) <= end(j) + fml = a.mk_le(ji.m_start->get_owner(), ji.m_end->get_owner()); + ctx.assert_expr(fml); + + time_t start_lb = std::numeric_limits::max(); + time_t end_ub = 0; for (job_resource const& jr : ji.m_resources) { + // resource(j) = r => end(j) <= end(j, r) + // resource(j) = r => start(j) <= lst(j, r, end(j, r)) unsigned r = jr.m_resource_id; app_ref res(u.mk_resource(r), m); expr_ref eq(m.mk_eq(job, res), m); expr_ref imp(m.mk_implies(eq, mk_le(ji.m_end, jr.m_end)), m); ctx.assert_expr(imp); - imp = m.mk_implies(eq, mk_le(ji.m_start, lst(j, r))); + time_t t; + if (!lst(j, r, t)) { + imp = m.mk_implies(eq, mk_le(ji.m_start, t)); + } + else { + imp = m.mk_not(eq); + } ctx.assert_expr(imp); disj.push_back(eq); + res_info const& ri = m_resources[r]; + start_lb = std::min(start_lb, ri.m_available[0].m_start); + end_ub = std::max(end_ub, ri.m_available.back().m_end); + } // resource(j) = r1 || ... || resource(j) = r_n expr_ref fml = mk_or(disj); ctx.assert_expr(fml); - // start(j) >= 0 - fml = mk_ge(ji.m_start, 0); + // start(j) >= start_lb + fml = mk_ge(ji.m_start, start_lb); ctx.assert_expr(fml); - // end(j) <= time_t::max - // is implied by resource(j) = r => end(j) <= end(j, r) + // end(j) <= end_ub + fml = mk_le(ji.m_end, end_ub); + ctx.assert_expr(fml); } for (unsigned r = 0; r < m_resources.size(); ++r) { - vector& available = m_resources[r].m_available; - res_available::compare cmp; - std::sort(available.begin(), available.end(), cmp); - for (unsigned i = 0; i < available.size(); ++i) { - if (i + 1 < available.size() && - available[i].m_end > available[i + 1].m_start) { + res_info& ri = m_resources[r]; + vector& available = ri.m_available; + if (available.empty()) continue; + app_ref res(u.mk_resource(r), m); + for (unsigned j : ri.m_jobs) { + // resource(j) == r => start(j) >= available[0].m_start; + app_ref job(u.mk_job(j), m); + expr_ref eq(m.mk_eq(job, res), m); + expr_ref ge(mk_ge(u.mk_start(j), available[0].m_start), m); + expr_ref fml(m.mk_implies(eq, ge), m); + ctx.assert_expr(fml); + } + for (unsigned i = 0; i + 1 < available.size(); ++i) { + if (available[i].m_end > available[i + 1].m_start) { throw default_exception("availability intervals should be disjoint"); } + for (unsigned j : ri.m_jobs) { + // jobs start within an interval. + // resource(j) == r => start(j) <= available[i].m_end || start(j) >= available[i + 1].m_start; + app_ref job(u.mk_job(j), m); + expr_ref eq(m.mk_eq(job, res), m); + expr_ref ge(mk_ge(u.mk_start(j), available[i + 1].m_start), m); + expr_ref le(mk_le(u.mk_start(j), available[i].m_end), m); + fml = m.mk_implies(eq, m.mk_or(le, ge)); + ctx.assert_expr(fml); + + // if job is not pre-emptable, start and end have to align within contiguous interval. + // resource(j) == r => end(j) <= available[i].m_end || start(j) >= available[i + 1].m_start + if (!m_jobs[j].m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { + le = mk_le(u.mk_end(j), available[i].m_end); + ge = mk_ge(u.mk_start(j), available[i+1].m_start); + fml = m.mk_implies(eq, m.mk_or(le, ge)); + ctx.assert_expr(fml); + } + } } } } @@ -473,6 +625,10 @@ namespace smt { if (ect(j, r, start(j)) > end(j)) { throw default_exception("job not assigned full capacity"); } + unsigned idx; + if (!resource_available(r, start(j), idx)) { + throw default_exception("resource is not available at job start time"); + } } for (unsigned r = 0; r < m_resources.size(); ++r) { // order jobs running on r by start, end-time intervals @@ -601,7 +757,7 @@ namespace smt { /** * Compute last start time for job on resource r. */ - time_t theory_jobscheduler::lst(unsigned j, unsigned r) { + bool theory_jobscheduler::lst(unsigned j, unsigned r, time_t& start) { job_resource const& jr = get_job_resource(j, r); vector& available = m_resources[r].m_available; unsigned j_load_pct = jr.m_loadpct; @@ -619,10 +775,10 @@ namespace smt { cap -= delta; } if (cap == 0) { - return start; + return true; } } - throw default_exception("there is insufficient capacity on the resource to run the job"); + return false; } }; diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 369cc1955..117303ff7 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -28,16 +28,20 @@ Revision History: namespace smt { typedef uint64_t time_t; - + class theory_jobscheduler : public theory { + public: + typedef map properties; + protected: struct job_resource { unsigned m_resource_id; // id of resource unsigned m_capacity; // amount of resource to use unsigned m_loadpct; // assuming loadpct time_t m_end; // must run before - job_resource(unsigned r, unsigned cap, unsigned loadpct, time_t end): - m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_end(end) {} + properties m_properties; + job_resource(unsigned r, unsigned cap, unsigned loadpct, time_t end, properties const& ps): + m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_end(end), m_properties(ps) {} }; struct job_time { @@ -53,22 +57,25 @@ namespace smt { }; struct job_info { + bool m_is_preemptable; // can job be pre-empted vector m_resources; // resources allowed to run job. u_map m_resource2index; // resource to index into vector enode* m_start; enode* m_end; enode* m_resource; - job_info(): m_start(nullptr), m_end(nullptr), m_resource(nullptr) {} + job_info(): m_is_preemptable(true), m_start(nullptr), m_end(nullptr), m_resource(nullptr) {} }; struct res_available { - unsigned m_loadpct; - time_t m_start; - time_t m_end; - res_available(unsigned load_pct, time_t start, time_t end): + unsigned m_loadpct; + time_t m_start; + time_t m_end; + properties m_properties; + res_available(unsigned load_pct, time_t start, time_t end, properties const& ps): m_loadpct(load_pct), m_start(start), - m_end(end) + m_end(end), + m_properties(ps) {} struct compare { bool operator()(res_available const& ra1, res_available const& ra2) const { @@ -135,8 +142,9 @@ namespace smt { public: // set up job/resource global constraints - void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end); - void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end); + void set_preemptable(unsigned j, bool is_preemptable); + void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end, properties const& ps); + void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps); void add_done(); // assignments @@ -150,7 +158,7 @@ namespace smt { // derived bounds time_t ect(unsigned j, unsigned r, time_t start); - time_t lst(unsigned j, unsigned r); + bool lst(unsigned j, unsigned r, time_t& t); time_t solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap); time_t solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap); @@ -165,10 +173,10 @@ namespace smt { // propagation void propagate_end_time(unsigned j, unsigned r); - void propagate_end_time_interval(unsigned j, unsigned r); void propagate_resource_energy(unsigned r); // final check constraints + bool constrain_end_time_interval(unsigned j, unsigned r); bool constrain_resource_energy(unsigned r); void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job); diff --git a/src/smt/theory_lra.cpp b/src/smt/theory_lra.cpp index 267412da6..6c66de91d 100644 --- a/src/smt/theory_lra.cpp +++ b/src/smt/theory_lra.cpp @@ -2658,13 +2658,22 @@ public: } } - bool get_value(enode* n, expr_ref& r) { + 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]; - rational val; if (m_solver->has_value(vi, val)) { 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; } @@ -2673,7 +2682,7 @@ public: } } - bool get_lower(enode* n, expr_ref& r) { + 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";); @@ -2681,29 +2690,36 @@ public: } lp::var_index vi = m_theory_var2var_index[v]; lp::constraint_index ci; - rational val; + return m_solver->has_lower_bound(vi, ci, val, is_strict); + } + + bool get_lower(enode* n, expr_ref& r) { bool is_strict; - if (m_solver->has_lower_bound(vi, ci, val, is_strict)) { + rational val; + if (get_lower(n, val, is_strict) && !is_strict) { r = a.mk_numeral(val, is_int(n)); return true; } - TRACE("arith", m_solver->print_constraints(tout << "does not have lower bound " << vi << "\n");); return false; } - bool get_upper(enode* n, expr_ref& r) { + 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; - rational val; + return m_solver->has_upper_bound(vi, ci, val, is_strict); + + } + + bool get_upper(enode* n, expr_ref& r) { bool is_strict; - if (m_solver->has_upper_bound(vi, ci, val, is_strict)) { + rational val; + if (get_upper(n, val, is_strict) && !is_strict) { r = a.mk_numeral(val, is_int(n)); return true; } - TRACE("arith", m_solver->print_constraints(tout << "does not have upper bound " << vi << "\n");); return false; } @@ -3132,6 +3148,9 @@ void theory_lra::init_model(model_generator & 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); } @@ -3141,6 +3160,12 @@ bool theory_lra::get_lower(enode* n, expr_ref& 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); +} bool theory_lra::validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { return m_imp->validate_eq_in_model(v1, v2, is_true); diff --git a/src/smt/theory_lra.h b/src/smt/theory_lra.h index 074b11ba7..811fa1812 100644 --- a/src/smt/theory_lra.h +++ b/src/smt/theory_lra.h @@ -80,6 +80,9 @@ namespace smt { bool get_value(enode* n, expr_ref& r) override; bool get_lower(enode* n, expr_ref& r); bool get_upper(enode* n, expr_ref& r); + bool get_value(enode* n, rational& r); + bool get_lower(enode* n, rational& r, bool& is_strict); + bool get_upper(enode* n, rational& r, bool& is_strict); bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const override; From 3478b8b924b21a5541b05e1d574789dbe15097a6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 12 Aug 2018 18:14:06 -0700 Subject: [PATCH 17/65] add js-model interfacing Signed-off-by: Nikolaj Bjorner --- src/ast/jobshop_decl_plugin.cpp | 35 +++++++- src/ast/jobshop_decl_plugin.h | 57 ++++++++++++- src/cmd_context/cmd_context.cpp | 3 + src/parsers/smt2/smt2parser.cpp | 16 +++- src/smt/smt_arith_value.cpp | 26 ++---- src/smt/theory_arith.h | 2 + src/smt/theory_arith_core.h | 14 +++ src/smt/theory_jobscheduler.cpp | 145 ++++++++++++++++++++++++++++---- src/smt/theory_jobscheduler.h | 15 +++- src/solver/smt_logics.cpp | 19 +++-- src/solver/smt_logics.h | 2 + 11 files changed, 275 insertions(+), 59 deletions(-) diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/jobshop_decl_plugin.cpp index 7a7b1b590..b1abc7fa3 100644 --- a/src/ast/jobshop_decl_plugin.cpp +++ b/src/ast/jobshop_decl_plugin.cpp @@ -23,14 +23,17 @@ Revision History: void jobshop_decl_plugin::set_manager(ast_manager* m, family_id fid) { decl_plugin::set_manager(m, fid); m_int_sort = m_manager->mk_sort(m_manager->mk_family_id("arith"), INT_SORT); + m_alist_sort = m_manager->mk_sort(symbol("AList"), sort_info(m_family_id, ALIST_SORT)); m_job_sort = m_manager->mk_sort(symbol("Job"), sort_info(m_family_id, JOB_SORT)); m_resource_sort = m_manager->mk_sort(symbol("Resource"), sort_info(m_family_id, RESOURCE_SORT)); m_manager->inc_ref(m_int_sort); m_manager->inc_ref(m_resource_sort); m_manager->inc_ref(m_job_sort); + m_manager->inc_ref(m_alist_sort); } void jobshop_decl_plugin::finalize() { + m_manager->dec_ref(m_alist_sort); m_manager->dec_ref(m_job_sort); m_manager->dec_ref(m_resource_sort); m_manager->dec_ref(m_int_sort); @@ -43,12 +46,13 @@ sort * jobshop_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parame switch (static_cast(k)) { case JOB_SORT: return m_job_sort; case RESOURCE_SORT: return m_resource_sort; + case ALIST_SORT: return m_alist_sort; default: UNREACHABLE(); return nullptr; } } func_decl * jobshop_decl_plugin::mk_func_decl( - decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const *, sort *) { + decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort *) { switch (static_cast(k)) { case OP_JS_JOB: check_arity(arity); @@ -70,6 +74,18 @@ func_decl * jobshop_decl_plugin::mk_func_decl( check_arity(arity); check_index1(num_parameters, parameters); return m_manager->mk_func_decl(symbol("job2resource"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_MODEL: + // has no parameters + // all arguments are of sort alist + return m_manager->mk_func_decl(symbol("js-model"), arity, domain, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_AL_KV: + // has two parameters, first is symbol + // has no arguments + return m_manager->mk_func_decl(symbol("kv"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_AL_LIST: + // has no parameters + // all arguments are of sort alist + return m_manager->mk_func_decl(symbol("alist"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); default: UNREACHABLE(); return nullptr; } @@ -96,17 +112,20 @@ bool jobshop_decl_plugin::is_value(app * e) const { } void jobshop_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { - if (logic == symbol("JOBSHOP")) { + if (logic == symbol("CSP")) { op_names.push_back(builtin_name("job", OP_JS_JOB)); op_names.push_back(builtin_name("resource", OP_JS_RESOURCE)); op_names.push_back(builtin_name("job-start", OP_JS_START)); op_names.push_back(builtin_name("job-end", OP_JS_END)); op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE)); + op_names.push_back(builtin_name("js-model", OP_JS_MODEL)); + op_names.push_back(builtin_name("kv", OP_AL_KV)); + op_names.push_back(builtin_name("alist", OP_AL_LIST)); } } void jobshop_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { - if (logic == symbol("JOBSHOP")) { + if (logic == symbol("CSP")) { sort_names.push_back(builtin_name("Job", JOB_SORT)); sort_names.push_back(builtin_name("Resource", RESOURCE_SORT)); } @@ -124,7 +143,7 @@ expr * jobshop_decl_plugin::get_some_value(sort * s) { jobshop_util::jobshop_util(ast_manager& m): m(m) { - m_fid = m.mk_family_id("jobshop"); + m_fid = m.mk_family_id("csp"); m_plugin = static_cast(m.get_plugin(m_fid)); } @@ -174,3 +193,11 @@ app* jobshop_util::mk_job2resource(unsigned j) { return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr)); } +bool jobshop_util::is_resource(expr* e, unsigned& r) { + return is_app_of(e, m_fid, OP_JS_RESOURCE) && (r = resource2id(e), true); +} + +bool jobshop_util::is_job(expr* e, unsigned& j) { + return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true); +} + diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/jobshop_decl_plugin.h index 48bdde6c1..f2cc7a849 100644 --- a/src/ast/jobshop_decl_plugin.h +++ b/src/ast/jobshop_decl_plugin.h @@ -68,7 +68,8 @@ Revision History: enum js_sort_kind { JOB_SORT, - RESOURCE_SORT + RESOURCE_SORT, + ALIST_SORT }; enum js_op_kind { @@ -76,7 +77,10 @@ enum js_op_kind { OP_JS_RESOURCE, // value of type resource OP_JS_START, // start time of a job OP_JS_END, // end time of a job - OP_JS_JOB2RESOURCE // resource associated with job + OP_JS_JOB2RESOURCE, // resource associated with job + OP_JS_MODEL, // jobscheduler model + OP_AL_KV, // key-value pair + OP_AL_LIST // tagged list }; class jobshop_decl_plugin : public decl_plugin { @@ -96,9 +100,11 @@ public: expr * get_some_value(sort * s) override; sort * mk_job_sort() const { return m_job_sort; } sort * mk_resource_sort() const { return m_resource_sort; } + sort * mk_alist_sort() const { return m_alist_sort; } private: sort* m_job_sort; sort* m_resource_sort; + sort* m_alist_sort; sort* m_int_sort; void check_arity(unsigned arity); @@ -116,12 +122,59 @@ public: sort* mk_resource_sort(); app* mk_job(unsigned j); + bool is_job(expr* e, unsigned& j); unsigned job2id(expr* j); app* mk_resource(unsigned r); + bool is_resource(expr* e, unsigned& r); unsigned resource2id(expr* r); app* mk_start(unsigned j); app* mk_end(unsigned j); app* mk_job2resource(unsigned j); + + // alist features + app* mk_kv(symbol const& key, rational const& r) { + parameter ps[2] = { parameter(key), parameter(r) }; + return m.mk_const(m.mk_func_decl(m_fid, OP_AL_KV, 2, ps, 0, (sort*const*)nullptr, nullptr)); + } + app* mk_kv(symbol const& key, symbol const& val) { + parameter ps[2] = { parameter(key), parameter(val) }; + return m.mk_const(m.mk_func_decl(m_fid, OP_AL_KV, 2, ps, 0, (sort*const*)nullptr, nullptr)); + } + app* mk_alist(symbol const& key, unsigned n, expr* const* args) { + parameter p(key); + return m.mk_app(m.mk_func_decl(m_fid, OP_AL_LIST, 1, &p, n, args, nullptr), n, args); + + } + bool is_kv(expr* e, symbol& key, rational& r) { + return + (is_app_of(e, m_fid, OP_AL_KV) && + to_app(e)->get_decl()->get_num_parameters() == 2 && + to_app(e)->get_decl()->get_parameter(1).is_rational() && + (r = to_app(e)->get_decl()->get_parameter(1).get_rational(), key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true)) || + (is_app_of(e, m_fid, OP_AL_KV) && + to_app(e)->get_decl()->get_num_parameters() == 2 && + to_app(e)->get_decl()->get_parameter(1).is_int() && + (r = rational(to_app(e)->get_decl()->get_parameter(1).get_int()), key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true)); + + + } + bool is_kv(expr* e, symbol& key, symbol& s) { + return is_app_of(e, m_fid, OP_AL_KV) && + to_app(e)->get_decl()->get_num_parameters() == 2 && + to_app(e)->get_decl()->get_parameter(1).is_symbol() && + (s = to_app(e)->get_decl()->get_parameter(1).get_symbol(), key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true); + } + + bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } + bool is_alist(expr* e) const { return is_app_of(e, m_fid, OP_AL_LIST); } + bool is_alist(expr* e, symbol& key) const { + return is_alist(e) && + to_app(e)->get_decl()->get_num_parameters() == 1 && + to_app(e)->get_decl()->get_parameter(0).is_symbol() && + (key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true); + } + + // app* mk_model(unsigned n, expr* const* alist); }; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index be3acc261..8387722b9 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -30,6 +30,7 @@ Notes: #include "ast/seq_decl_plugin.h" #include "ast/pb_decl_plugin.h" #include "ast/fpa_decl_plugin.h" +#include "ast/jobshop_decl_plugin.h" #include "ast/ast_pp.h" #include "ast/rewriter/var_subst.h" #include "ast/pp.h" @@ -695,6 +696,7 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("pb"), alloc(pb_decl_plugin), logic_has_pb()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin), !has_logic()); + register_plugin(symbol("csp"), alloc(jobshop_decl_plugin), smt_logics::logic_is_csp(m_logic)); } else { // the manager was created by an external module @@ -709,6 +711,7 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); load_plugin(symbol("pb"), logic_has_pb(), fids); + load_plugin(symbol("csp"), smt_logics::logic_is_csp(m_logic), fids); for (family_id fid : fids) { decl_plugin * p = m_manager->get_plugin(fid); if (p) { diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 403ea4c85..d1b4847cc 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -1523,8 +1523,20 @@ namespace smt2 { unsigned num_indices = 0; while (!curr_is_rparen()) { if (curr_is_int()) { - unsigned u = curr_unsigned(); - m_param_stack.push_back(parameter(u)); + if (!curr_numeral().is_unsigned()) { + m_param_stack.push_back(parameter(curr_numeral())); + } + else { + m_param_stack.push_back(parameter(curr_unsigned())); + } + next(); + } + else if (curr_is_float()) { + m_param_stack.push_back(parameter(curr_numeral())); + next(); + } + else if (curr_is_keyword()) { + m_param_stack.push_back(parameter(curr_id())); next(); } else if (curr_is_identifier() || curr_is_lparen()) { diff --git a/src/smt/smt_arith_value.cpp b/src/smt/smt_arith_value.cpp index ecf924111..ce4c0d9a9 100644 --- a/src/smt/smt_arith_value.cpp +++ b/src/smt/smt_arith_value.cpp @@ -30,7 +30,6 @@ namespace smt { bool arith_value::get_lo(expr* e, rational& lo, bool& is_strict) { if (!m_ctx.e_internalized(e)) return false; - expr_ref _lo(m); family_id afid = a.get_family_id(); is_strict = false; enode* next = m_ctx.get_enode(e), *n = next; @@ -42,15 +41,9 @@ namespace smt { theory_i_arith* thi = dynamic_cast(th); theory_lra* thr = dynamic_cast(th); do { - if (tha && tha->get_lower(next, _lo) && a.is_numeral(_lo, lo1)) { - if (!found || lo1 > lo) lo = lo1; - found = true; - } - else if (thi && thi->get_lower(next, _lo) && a.is_numeral(_lo, lo1)) { - if (!found || lo1 > lo) lo = lo1; - found = true; - } - else if (thr && thr->get_lower(next, lo1, is_strict1)) { + if ((tha && tha->get_lower(next, lo1, is_strict1)) || + (thi && thi->get_lower(next, lo1, is_strict1)) || + (thr && thr->get_lower(next, lo1, is_strict1))) { if (!found || lo1 > lo || (lo == lo1 && is_strict1)) lo = lo1, is_strict = is_strict1; found = true; } @@ -62,7 +55,6 @@ namespace smt { bool arith_value::get_up(expr* e, rational& up, bool& is_strict) { if (!m_ctx.e_internalized(e)) return false; - expr_ref _up(m); family_id afid = a.get_family_id(); is_strict = false; enode* next = m_ctx.get_enode(e), *n = next; @@ -73,15 +65,9 @@ namespace smt { theory_i_arith* thi = dynamic_cast(th); theory_lra* thr = dynamic_cast(th); do { - if (tha && tha->get_upper(next, _up) && a.is_numeral(_up, up1)) { - if (!found || up1 < up) up = up1; - found = true; - } - else if (thi && thi->get_upper(next, _up) && a.is_numeral(_up, up1)) { - if (!found || up1 < up) up = up1; - found = true; - } - else if (thr && thr->get_upper(next, up1, is_strict1)) { + if ((tha && tha->get_upper(next, up1, is_strict1)) || + (thi && thi->get_upper(next, up1, is_strict1)) || + (thr && thr->get_upper(next, up1, is_strict1))) { if (!found || up1 < up || (up1 == up && is_strict1)) up = up1, is_strict = is_strict1; found = true; } diff --git a/src/smt/theory_arith.h b/src/smt/theory_arith.h index 4b3ee52f6..a2c6c1191 100644 --- a/src/smt/theory_arith.h +++ b/src/smt/theory_arith.h @@ -1071,6 +1071,8 @@ namespace smt { bool get_lower(enode* n, expr_ref& r); bool get_upper(enode* n, expr_ref& r); + bool get_lower(enode* n, rational& r, bool &is_strict); + bool get_upper(enode* n, rational& r, bool &is_strict); bool to_expr(inf_numeral const& val, bool is_int, expr_ref& r); diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 54f61a09a..1eb91f1fc 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -3303,6 +3303,20 @@ namespace smt { return b && to_expr(b->get_value(), is_int(v), r); } + template + bool theory_arith::get_lower(enode * n, rational& r, bool& is_strict) { + theory_var v = n->get_th_var(get_id()); + bound* b = (v == null_theory_var) ? nullptr : lower(v); + return b && (r = b->get_value().get_rational().to_rational(), is_strict = b->get_value().get_infinitesimal().is_pos(), true); + } + + template + bool theory_arith::get_upper(enode * n, rational& r, bool& is_strict) { + theory_var v = n->get_th_var(get_id()); + bound* b = (v == null_theory_var) ? nullptr : upper(v); + return b && (r = b->get_value().get_rational().to_rational(), is_strict = b->get_value().get_infinitesimal().is_neg(), true); + } + // ----------------------------------- // // Backtracking diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 173d44c52..5064fc037 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -65,7 +65,7 @@ namespace smt { unsigned j = u.job2id(term); app_ref start(u.mk_start(j), m); app_ref end(u.mk_end(j), m); - app_ref res(u.mk_resource(j), m); + app_ref res(u.mk_job2resource(j), m); if (!ctx.e_internalized(start)) ctx.internalize(start, false); if (!ctx.e_internalized(end)) ctx.internalize(end, false); if (!ctx.e_internalized(res)) ctx.internalize(res, false); @@ -75,7 +75,7 @@ namespace smt { m_jobs.reserve(j + 1); m_jobs[j].m_start = ctx.get_enode(start); m_jobs[j].m_end = ctx.get_enode(end); - m_jobs[j].m_resource = ctx.get_enode(res); + m_jobs[j].m_job2resource = ctx.get_enode(res); ctx.attach_th_var(e, this, v); break; } @@ -99,6 +99,89 @@ namespace smt { return true; } + bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) { + SASSERT(u.is_model(atom)); + for (expr* arg : *atom) { + internalize_cmd(arg); + } + add_done(); + return true; + } + + // TBD: stronger parameter validation + void theory_jobscheduler::internalize_cmd(expr* cmd) { + symbol key, val; + rational r; + if (u.is_kv(cmd, key, r)) { + if (key == ":set-preemptable" && r.is_unsigned()) { + set_preemptable(r.get_unsigned(), true); + return; + } + warning_msg("command not recognized"); + } + else if (u.is_alist(cmd, key) && key == ":add-job-resource") { + properties ps; + unsigned j = 0, res = 0, cap = 0, loadpct = 100; + time_t end = std::numeric_limits::max(); + for (expr* arg : *to_app(cmd)) { + if (u.is_kv(arg, key, r)) { + if (key == ":job") { + j = r.get_unsigned(); + } + else if (key == ":resource") { + res = r.get_unsigned(); + } + else if (key == ":capacity") { + cap = r.get_unsigned(); + } + else if (key == ":loadpct") { + loadpct = r.get_unsigned(); + } + else if (key == ":end") { + end = r.get_uint64(); + } + } + else if (u.is_alist(arg, key) && key == ":properties") { + // TBD + } + } + if (cap > 0) { + add_job_resource(j, res, cap, loadpct, end, ps); + } + else { + warning_msg("no job capacity provided"); + } + } + else if (u.is_alist(cmd, key) && key == ":add-resource-available") { + properties ps; + unsigned res = 0, loadpct = 100; + time_t start = 0, end = 0; + for (expr* arg : *to_app(cmd)) { + if (u.is_kv(arg, key, r)) { + if (key == ":resource") { + res = r.get_unsigned(); + } + else if (key == ":start") { + start = r.get_unsigned(); + } + else if (key == ":end") { + end = r.get_unsigned(); + } + else if (key == ":loadpct") { + loadpct = r.get_unsigned(); + } + } + else if (u.is_alist(arg, key) && key == ":properties") { + // TBD + } + add_resource_available(res, loadpct, start, end, ps); + } + + } + else { + warning_msg("command not recognized"); + } + } void theory_jobscheduler::push_scope_eh() { } @@ -214,7 +297,7 @@ namespace smt { if (ctx.get_assignment(start_ge_lo) != l_true) { return; } - enode_pair eq(ji.m_resource, ctx.get_enode(u.mk_resource(r))); + enode_pair eq(ji.m_job2resource, resource2enode(r)); if (eq.first->get_root() != eq.second->get_root()) { return; } @@ -368,7 +451,7 @@ namespace smt { } theory_jobscheduler::theory_jobscheduler(ast_manager& m): - theory(m.get_family_id("jobshop")), m(m), u(m), a(m) { + theory(m.get_family_id("csp")), m(m), u(m), a(m) { } std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { @@ -423,51 +506,76 @@ namespace smt { return alloc(theory_jobscheduler, new_ctx->get_manager()); } - time_t theory_jobscheduler::est(unsigned j) { + time_t theory_jobscheduler::get_lo(expr* e) { arith_value av(get_context()); rational val; bool is_strict; - if (av.get_lo(u.mk_start(j), val, is_strict) && !is_strict && val.is_uint64()) { + if (av.get_lo(e, val, is_strict) && !is_strict && val.is_uint64()) { return val.get_uint64(); } return 0; } - time_t theory_jobscheduler::lst(unsigned j) { + time_t theory_jobscheduler::get_up(expr* e) { arith_value av(get_context()); rational val; bool is_strict; - if (av.get_up(u.mk_start(j), val, is_strict) && !is_strict && val.is_uint64()) { + if (av.get_up(e, val, is_strict) && !is_strict && val.is_uint64()) { return val.get_uint64(); } return std::numeric_limits::max(); } - time_t theory_jobscheduler::ect(unsigned j) { - NOT_IMPLEMENTED_YET(); + time_t theory_jobscheduler::get_value(expr* e) { + arith_value av(get_context()); + rational val; + if (av.get_value(e, val) && val.is_uint64()) { + return val.get_uint64(); + } return 0; + } + + time_t theory_jobscheduler::est(unsigned j) { + return get_lo(m_jobs[j].m_start->get_owner()); + } + + time_t theory_jobscheduler::lst(unsigned j) { + return get_up(m_jobs[j].m_start->get_owner()); + } + + time_t theory_jobscheduler::ect(unsigned j) { + return get_lo(m_jobs[j].m_end->get_owner()); } time_t theory_jobscheduler::lct(unsigned j) { - NOT_IMPLEMENTED_YET(); - return 0; + return get_up(m_jobs[j].m_end->get_owner()); } time_t theory_jobscheduler::start(unsigned j) { - NOT_IMPLEMENTED_YET(); - return 0; + return get_value(m_jobs[j].m_start->get_owner()); } time_t theory_jobscheduler::end(unsigned j) { - NOT_IMPLEMENTED_YET(); - return 0; + return get_value(m_jobs[j].m_end->get_owner()); } unsigned theory_jobscheduler::resource(unsigned j) { - NOT_IMPLEMENTED_YET(); + unsigned r; + enode* next = m_jobs[j].m_job2resource, *n = next; + do { + if (u.is_resource(next->get_owner(), r)) { + return r; + } + next = next->get_next(); + } + while (next != n); return 0; } + enode* theory_jobscheduler::resource2enode(unsigned r) { + return get_context().get_enode(u.mk_resource(r)); + } + void theory_jobscheduler::set_preemptable(unsigned j, bool is_preemptable) { m_jobs.reserve(j + 1); m_jobs[j].m_is_preemptable = is_preemptable; @@ -494,7 +602,8 @@ namespace smt { SASSERT(1 <= max_loadpct && max_loadpct <= 100); SASSERT(start <= end); m_resources.reserve(r + 1); - m_resources[r].m_available.push_back(res_available(max_loadpct, start, end, ps)); + res_info& ri = m_resources[r]; + ri.m_available.push_back(res_available(max_loadpct, start, end, ps)); } /* diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 117303ff7..3647e6a1b 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -62,8 +62,8 @@ namespace smt { u_map m_resource2index; // resource to index into vector enode* m_start; enode* m_end; - enode* m_resource; - job_info(): m_is_preemptable(true), m_start(nullptr), m_end(nullptr), m_resource(nullptr) {} + enode* m_job2resource; + job_info(): m_is_preemptable(false), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr) {} }; struct res_available { @@ -88,7 +88,8 @@ namespace smt { unsigned_vector m_jobs; // jobs allocated to run on resource vector m_available; // time intervals where resource is available time_t m_end; // can't run after - res_info(): m_end(std::numeric_limits::max()) {} + enode* m_resource; + res_info(): m_end(std::numeric_limits::max()), m_resource(nullptr) {} }; ast_manager& m; @@ -102,7 +103,7 @@ namespace smt { theory_var mk_var(enode * n) override; - bool internalize_atom(app * atom, bool gate_ctx) override { return false; } + bool internalize_atom(app * atom, bool gate_ctx) override; bool internalize_term(app * term) override; @@ -154,7 +155,11 @@ namespace smt { time_t lct(unsigned j); // last completion time time_t start(unsigned j); // start time of job j time_t end(unsigned j); // end time of job j + time_t get_lo(expr* e); + time_t get_up(expr* e); + time_t get_value(expr* e); unsigned resource(unsigned j); // resource of job j + enode* resource2enode(unsigned r); // derived bounds time_t ect(unsigned j, unsigned r, time_t start); @@ -201,6 +206,8 @@ namespace smt { literal mk_le(enode* l, enode* r); literal mk_literal(expr* e); + void internalize_cmd(expr* cmd); + std::ostream& display(std::ostream & out, res_info const& r) const; std::ostream& display(std::ostream & out, res_available const& r) const; std::ostream& display(std::ostream & out, job_info const& r) const; diff --git a/src/solver/smt_logics.cpp b/src/solver/smt_logics.cpp index 59a9a1562..d0fd8f809 100644 --- a/src/solver/smt_logics.cpp +++ b/src/solver/smt_logics.cpp @@ -22,7 +22,7 @@ Revision History: bool smt_logics::supported_logic(symbol const & s) { - return logic_has_uf(s) || logic_is_all(s) || logic_has_fd(s) || + return logic_has_uf(s) || logic_is_allcsp(s) || logic_has_fd(s) || logic_has_arith(s) || logic_has_bv(s) || logic_has_array(s) || logic_has_seq(s) || logic_has_str(s) || logic_has_horn(s) || logic_has_fpa(s); @@ -83,7 +83,7 @@ bool smt_logics::logic_has_arith(symbol const & s) { s == "QF_FPBV" || s == "QF_BVFP" || s == "QF_S" || - s == "ALL" || + logic_is_allcsp(s) || s == "QF_FD" || s == "HORN" || s == "QF_FPLRA"; @@ -102,7 +102,7 @@ bool smt_logics::logic_has_bv(symbol const & s) { s == "QF_BVRE" || s == "QF_FPBV" || s == "QF_BVFP" || - s == "ALL" || + logic_is_allcsp(s) || s == "QF_FD" || s == "HORN"; } @@ -123,22 +123,22 @@ bool smt_logics::logic_has_array(symbol const & s) { s == "AUFNIRA" || s == "AUFBV" || s == "ABV" || - s == "ALL" || + logic_is_allcsp(s) || s == "QF_ABV" || s == "QF_AUFBV" || s == "HORN"; } bool smt_logics::logic_has_seq(symbol const & s) { - return s == "QF_BVRE" || s == "QF_S" || s == "ALL"; + return s == "QF_BVRE" || s == "QF_S" || logic_is_allcsp(s); } bool smt_logics::logic_has_str(symbol const & s) { - return s == "QF_S" || s == "ALL"; + return s == "QF_S" || logic_is_allcsp(s); } bool smt_logics::logic_has_fpa(symbol const & s) { - return s == "QF_FP" || s == "QF_FPBV" || s == "QF_BVFP" || s == "QF_FPLRA" || s == "ALL"; + return s == "QF_FP" || s == "QF_FPBV" || s == "QF_BVFP" || s == "QF_FPLRA" || logic_is_allcsp(s); } bool smt_logics::logic_has_uf(symbol const & s) { @@ -150,9 +150,10 @@ bool smt_logics::logic_has_horn(symbol const& s) { } bool smt_logics::logic_has_pb(symbol const& s) { - return s == "QF_FD" || s == "ALL" || logic_has_horn(s); + return s == "QF_FD" || logic_is_allcsp(s) || logic_has_horn(s); } bool smt_logics::logic_has_datatype(symbol const& s) { - return s == "QF_FD" || s == "ALL" || s == "QF_DT"; + return s == "QF_FD" || logic_is_allcsp(s) || s == "QF_DT"; } + diff --git a/src/solver/smt_logics.h b/src/solver/smt_logics.h index 702431cdd..4382f575b 100644 --- a/src/solver/smt_logics.h +++ b/src/solver/smt_logics.h @@ -25,6 +25,8 @@ public: static bool supported_logic(symbol const & s); static bool logic_has_reals_only(symbol const& l); static bool logic_is_all(symbol const& s) { return s == "ALL"; } + static bool logic_is_csp(symbol const& s) { return s == "CSP"; } + static bool logic_is_allcsp(symbol const& s) { return logic_is_all(s) || logic_is_csp(s); } static bool logic_has_uf(symbol const& s); static bool logic_has_arith(symbol const & s); static bool logic_has_bv(symbol const & s); From 540baa88f4b4b2849a606fe7db72956d6b39cbcd Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 13 Aug 2018 17:08:34 -0700 Subject: [PATCH 18/65] na Signed-off-by: Nikolaj Bjorner --- src/smt/smt_setup.cpp | 10 +++ src/smt/smt_setup.h | 1 + src/smt/theory_jobscheduler.cpp | 143 ++++++++++++++++++++------------ src/smt/theory_jobscheduler.h | 5 +- 4 files changed, 106 insertions(+), 53 deletions(-) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 9e38fef69..585e7e841 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -35,6 +35,7 @@ Revision History: #include "smt/theory_pb.h" #include "smt/theory_fpa.h" #include "smt/theory_str.h" +#include "smt/theory_jobscheduler.h" namespace smt { @@ -119,6 +120,8 @@ namespace smt { setup_UFLRA(); else if (m_logic == "LRA") setup_LRA(); + else if (m_logic == "CSP") + setup_CSP(); else if (m_logic == "QF_FP") setup_QF_FP(); else if (m_logic == "QF_FPBV" || m_logic == "QF_BVFP") @@ -196,6 +199,8 @@ namespace smt { setup_QF_DT(); else if (m_logic == "LRA") setup_LRA(); + else if (m_logic == "CSP") + setup_CSP(); else setup_unknown(st); } @@ -916,6 +921,11 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_seq, m_manager, m_params)); } + void setup::setup_CSP() { + setup_unknown(); + m_context.register_plugin(alloc(smt::theory_jobscheduler, m_manager)); + } + void setup::setup_unknown() { static_features st(m_manager); ptr_vector fmls; diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index ed0ab066d..61bfa386e 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -81,6 +81,7 @@ namespace smt { void setup_QF_FPBV(); void setup_QF_S(); void setup_LRA(); + void setup_CSP(); void setup_AUFLIA(bool simple_array = true); void setup_AUFLIA(static_features const & st); void setup_AUFLIRA(bool simple_array = true); diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 5064fc037..70c891b8d 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -44,6 +44,7 @@ Features: --*/ +#include "ast/ast_pp.h" #include "smt/theory_jobscheduler.h" #include "smt/smt_context.h" #include "smt/smt_arith_value.h" @@ -100,6 +101,7 @@ namespace smt { } bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) { + TRACE("csp", tout << mk_pp(atom, m) << "\n";); SASSERT(u.is_model(atom)); for (expr* arg : *atom) { internalize_cmd(arg); @@ -112,77 +114,108 @@ namespace smt { void theory_jobscheduler::internalize_cmd(expr* cmd) { symbol key, val; rational r; - if (u.is_kv(cmd, key, r)) { - if (key == ":set-preemptable" && r.is_unsigned()) { - set_preemptable(r.get_unsigned(), true); - return; - } - warning_msg("command not recognized"); + if (u.is_kv(cmd, key, r) && key == ":set-preemptable" && r.is_unsigned()) { + set_preemptable(r.get_unsigned(), true); } else if (u.is_alist(cmd, key) && key == ":add-job-resource") { properties ps; unsigned j = 0, res = 0, cap = 0, loadpct = 100; time_t end = std::numeric_limits::max(); + bool has_j = false, has_res = false, has_cap = false, has_load = false, has_end = false; for (expr* arg : *to_app(cmd)) { if (u.is_kv(arg, key, r)) { - if (key == ":job") { + if (key == ":job" && r.is_unsigned()) { j = r.get_unsigned(); + has_j = true; } - else if (key == ":resource") { + else if (key == ":resource" && r.is_unsigned()) { res = r.get_unsigned(); + has_res = true; } - else if (key == ":capacity") { + else if (key == ":capacity" && r.is_unsigned()) { cap = r.get_unsigned(); + has_cap = true; } - else if (key == ":loadpct") { + else if (key == ":loadpct" && r.is_unsigned()) { loadpct = r.get_unsigned(); + has_load = true; } - else if (key == ":end") { + else if (key == ":end" && r.is_uint64()) { end = r.get_uint64(); + has_end = true; + } + else { + unrecognized_argument(arg); } } else if (u.is_alist(arg, key) && key == ":properties") { // TBD } + else { + unrecognized_argument(arg); + } } - if (cap > 0) { - add_job_resource(j, res, cap, loadpct, end, ps); - } - else { - warning_msg("no job capacity provided"); - } + if (!has_j) invalid_argument(":job argument expected ", cmd); + if (!has_res) invalid_argument(":resource argument expected ", cmd); + if (!has_cap) invalid_argument(":capacity argument expected ", cmd); + if (!has_load) invalid_argument(":loadpct argument expected ", cmd); + if (!has_end) invalid_argument(":end argument expected ", cmd); + if (cap == 0) invalid_argument(":capacity should be positive ", cmd); + add_job_resource(j, res, cap, loadpct, end, ps); } else if (u.is_alist(cmd, key) && key == ":add-resource-available") { properties ps; unsigned res = 0, loadpct = 100; time_t start = 0, end = 0; + bool has_start = false, has_res = false, has_load = false, has_end = false; + for (expr* arg : *to_app(cmd)) { if (u.is_kv(arg, key, r)) { - if (key == ":resource") { + if (key == ":resource" && r.is_unsigned()) { res = r.get_unsigned(); + has_res = true; } - else if (key == ":start") { - start = r.get_unsigned(); + else if (key == ":start" && r.is_uint64()) { + start = r.get_uint64(); + has_start = true; } - else if (key == ":end") { - end = r.get_unsigned(); + else if (key == ":end" && r.is_uint64()) { + end = r.get_uint64(); + has_end = true; } - else if (key == ":loadpct") { + else if (key == ":loadpct" && r.is_unsigned()) { loadpct = r.get_unsigned(); + has_load = true; + } + else { + unrecognized_argument(arg); } } else if (u.is_alist(arg, key) && key == ":properties") { // TBD } + else { + unrecognized_argument(arg); + } + if (!has_res) invalid_argument(":resource argument expected ", cmd); + if (!has_load) invalid_argument(":loadpct argument expected ", cmd); + if (!has_end) invalid_argument(":end argument expected ", cmd); + if (!has_start) invalid_argument(":start argument expected ", cmd); add_resource_available(res, loadpct, start, end, ps); } } else { - warning_msg("command not recognized"); + invalid_argument("command not recognized ", cmd); } } + void theory_jobscheduler::invalid_argument(char const* msg, expr* arg) { + std::ostringstream strm; + strm << msg << mk_pp(arg, m); + throw default_exception(strm.str()); + } + void theory_jobscheduler::push_scope_eh() { } @@ -256,29 +289,34 @@ namespace smt { * iterator of job overlaps. */ theory_jobscheduler::job_overlap::job_overlap(vector& starts, vector& ends): - m_starts(starts), m_ends(ends), s_idx(0), e_idx(0) { + m_start(0), m_starts(starts), m_ends(ends), s_idx(0), e_idx(0) { job_time::compare cmp; std::sort(starts.begin(), starts.end(), cmp); std::sort(ends.begin(), ends.end(), cmp); } - bool theory_jobscheduler::job_overlap::next(time_t& start) { + bool theory_jobscheduler::job_overlap::next() { if (s_idx == m_starts.size()) { return false; } - while (s_idx < m_starts.size() && m_starts[s_idx].m_time <= start) { - m_jobs.insert(m_starts[s_idx].m_job); - ++s_idx; - } - // remove jobs that end before start. - while (e_idx < m_ends.size() && m_ends[s_idx].m_time < start) { - m_jobs.remove(m_ends[e_idx].m_job); - ++e_idx; - } - // TBD: check logic - if (s_idx < m_starts.size()) { - start = m_starts[s_idx].m_time; + do { + m_start = std::max(m_start, m_starts[s_idx].m_time); + + // add jobs that start before or at m_start + while (s_idx < m_starts.size() && m_starts[s_idx].m_time <= m_start) { + m_jobs.insert(m_starts[s_idx].m_job); + ++s_idx; + } + // remove jobs that end before m_start. + while (e_idx < m_ends.size() && m_ends[s_idx].m_time < m_start) { + m_jobs.remove(m_ends[e_idx].m_job); + ++e_idx; + } } + // as long as set of jobs increments, add to m_start + while (s_idx < m_starts.size() && e_idx < m_ends.size() && + m_starts[s_idx].m_time <= m_ends[e_idx].m_time); + return true; } @@ -360,11 +398,13 @@ namespace smt { return false; } literal_vector lits; - lits.push_back(~mk_eq(u.mk_job2resource(j), u.mk_resource(r), false)); - lits.push_back(~mk_ge_lit(u.mk_start(j), t0)); - lits.push_back(~mk_le_lit(u.mk_start(j), t1)); - expr_ref rhs(a.mk_add(u.mk_start(j), a.mk_int(rational(delta, rational::ui64()))), m); - lits.push_back(mk_eq(u.mk_end(j), rhs, false)); + expr* start_e = m_jobs[j].m_start->get_owner(); + expr* end_e = m_jobs[j].m_end->get_owner(); + lits.push_back(~mk_eq(m_jobs[j].m_job2resource->get_owner(), u.mk_resource(r), false)); + lits.push_back(~mk_ge_lit(start_e, t0)); + lits.push_back(~mk_le_lit(start_e, t1)); + expr_ref rhs(a.mk_add(start_e, a.mk_int(rational(delta, rational::ui64()))), m); + lits.push_back(mk_eq(end_e, rhs, false)); context& ctx = get_context(); ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_AUX_LEMMA, nullptr); return true; @@ -388,8 +428,7 @@ namespace smt { } } job_overlap overlap(starts, ends); - time_t start = 0; - while (overlap.next(start)) { + while (overlap.next()) { unsigned cap = 0; auto const& jobs = overlap.jobs(); for (auto j : jobs) { @@ -478,6 +517,7 @@ namespace smt { } void theory_jobscheduler::display(std::ostream & out) const { + out << "jobscheduler:\n"; for (unsigned j = 0; j < m_jobs.size(); ++j) { display(out << "job " << j << ":\n", m_jobs[j]); } @@ -685,7 +725,7 @@ namespace smt { // resource(j) == r => start(j) >= available[0].m_start; app_ref job(u.mk_job(j), m); expr_ref eq(m.mk_eq(job, res), m); - expr_ref ge(mk_ge(u.mk_start(j), available[0].m_start), m); + expr_ref ge(mk_ge(m_jobs[j].m_start, available[0].m_start), m); expr_ref fml(m.mk_implies(eq, ge), m); ctx.assert_expr(fml); } @@ -698,16 +738,16 @@ namespace smt { // resource(j) == r => start(j) <= available[i].m_end || start(j) >= available[i + 1].m_start; app_ref job(u.mk_job(j), m); expr_ref eq(m.mk_eq(job, res), m); - expr_ref ge(mk_ge(u.mk_start(j), available[i + 1].m_start), m); - expr_ref le(mk_le(u.mk_start(j), available[i].m_end), m); + expr_ref ge(mk_ge(m_jobs[j].m_start, available[i + 1].m_start), m); + expr_ref le(mk_le(m_jobs[j].m_start, available[i].m_end), m); fml = m.mk_implies(eq, m.mk_or(le, ge)); ctx.assert_expr(fml); // if job is not pre-emptable, start and end have to align within contiguous interval. // resource(j) == r => end(j) <= available[i].m_end || start(j) >= available[i + 1].m_start if (!m_jobs[j].m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { - le = mk_le(u.mk_end(j), available[i].m_end); - ge = mk_ge(u.mk_start(j), available[i+1].m_start); + le = mk_le(m_jobs[j].m_end, available[i].m_end); + ge = mk_ge(m_jobs[j].m_start, available[i+1].m_start); fml = m.mk_implies(eq, m.mk_or(le, ge)); ctx.assert_expr(fml); } @@ -742,9 +782,8 @@ namespace smt { for (unsigned r = 0; r < m_resources.size(); ++r) { // order jobs running on r by start, end-time intervals // then consume ordered list to find jobs in scope. - time_t start = 0; job_overlap overlap(start_times[r], end_times[r]); - while (overlap.next(start)) { + while (overlap.next()) { // check that sum of job loads does not exceed 100% unsigned cap = 0; for (auto j : overlap.jobs()) { diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 3647e6a1b..7c5520548 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -187,12 +187,13 @@ namespace smt { void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job); class job_overlap { + time_t m_start; vector & m_starts, &m_ends; unsigned s_idx, e_idx; // index into starts/ends uint_set m_jobs; public: job_overlap(vector& starts, vector& ends); - bool next(time_t& start); + bool next(); uint_set const& jobs() const { return m_jobs; } }; @@ -207,6 +208,8 @@ namespace smt { literal mk_literal(expr* e); void internalize_cmd(expr* cmd); + void unrecognized_argument(expr* arg) { invalid_argument("unrecognized argument ", arg); } + void invalid_argument(char const* msg, expr* arg); std::ostream& display(std::ostream & out, res_info const& r) const; std::ostream& display(std::ostream & out, res_available const& r) const; From a096ec648c96387804b561397c39420222ba64b0 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 13 Aug 2018 17:11:22 -0700 Subject: [PATCH 19/65] na Signed-off-by: Nikolaj Bjorner --- src/smt/theory_jobscheduler.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 70c891b8d..c640c346a 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -197,13 +197,12 @@ namespace smt { else { unrecognized_argument(arg); } - if (!has_res) invalid_argument(":resource argument expected ", cmd); - if (!has_load) invalid_argument(":loadpct argument expected ", cmd); - if (!has_end) invalid_argument(":end argument expected ", cmd); - if (!has_start) invalid_argument(":start argument expected ", cmd); - add_resource_available(res, loadpct, start, end, ps); } - + if (!has_res) invalid_argument(":resource argument expected ", cmd); + if (!has_load) invalid_argument(":loadpct argument expected ", cmd); + if (!has_end) invalid_argument(":end argument expected ", cmd); + if (!has_start) invalid_argument(":start argument expected ", cmd); + add_resource_available(res, loadpct, start, end, ps); } else { invalid_argument("command not recognized ", cmd); From d55fe1ac59072c962b0d0d0ad28e718a09b0b6d5 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 09:41:43 -0700 Subject: [PATCH 20/65] na' Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 1 + src/ast/jobshop_decl_plugin.cpp | 60 +++++- src/ast/jobshop_decl_plugin.h | 7 + src/smt/smt_model_generator.cpp | 5 +- src/smt/theory_jobscheduler.cpp | 371 +++++++++++++++----------------- src/smt/theory_jobscheduler.h | 32 ++- 6 files changed, 261 insertions(+), 215 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 11a15492c..e889d0233 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1056,6 +1056,7 @@ sort* basic_decl_plugin::join(sort* s1, sort* s2) { return s2; } std::ostringstream buffer; + SASSERT(false); buffer << "Sorts " << mk_pp(s1, *m_manager) << " and " << mk_pp(s2, *m_manager) << " are incompatible"; throw ast_exception(buffer.str()); } diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/jobshop_decl_plugin.cpp index b1abc7fa3..79a2cf177 100644 --- a/src/ast/jobshop_decl_plugin.cpp +++ b/src/ast/jobshop_decl_plugin.cpp @@ -73,7 +73,9 @@ func_decl * jobshop_decl_plugin::mk_func_decl( case OP_JS_JOB2RESOURCE: check_arity(arity); check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job2resource"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + return m_manager->mk_func_decl(symbol("job2resource"), 0, (sort* const*)nullptr, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + + case OP_JS_MODEL: // has no parameters // all arguments are of sort alist @@ -86,6 +88,24 @@ func_decl * jobshop_decl_plugin::mk_func_decl( // has no parameters // all arguments are of sort alist return m_manager->mk_func_decl(symbol("alist"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_JOB_RESOURCE: + if (arity != 5) m_manager->raise_exception("add-job-resource expects 5 arguments"); + if (domain[0] != m_job_sort) m_manager->raise_exception("first argument of add-job-resource expects should be a job"); + if (domain[1] != m_resource_sort) m_manager->raise_exception("second argument of add-job-resource expects should be a resource"); + if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-job-resource expects should be an integer"); + if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-job-resource expects should be an integer"); + if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-job-resource expects should be an integer"); + return m_manager->mk_func_decl(symbol("add-job-resource"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_RESOURCE_AVAILABLE: + if (arity != 4) m_manager->raise_exception("add-resource-available expects 4 arguments"); + if (domain[0] != m_resource_sort) m_manager->raise_exception("first argument of add-resource-available expects should be a resource"); + if (domain[1] != m_int_sort) m_manager->raise_exception("2nd argument of add-resource-available expects should be an integer"); + if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-resource-available expects should be an integer"); + if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-resource-available expects should be an integer"); + return m_manager->mk_func_decl(symbol("add-resource-available"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_JS_JOB_PREEMPTABLE: + if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("set-preemptable expects one argument, which is a job"); + return m_manager->mk_func_decl(symbol("set-preemptable"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); default: UNREACHABLE(); return nullptr; } @@ -121,6 +141,10 @@ void jobshop_decl_plugin::get_op_names(svector & op_names, symbol op_names.push_back(builtin_name("js-model", OP_JS_MODEL)); op_names.push_back(builtin_name("kv", OP_AL_KV)); op_names.push_back(builtin_name("alist", OP_AL_LIST)); + op_names.push_back(builtin_name("add-job-resource", OP_JS_JOB_RESOURCE)); + op_names.push_back(builtin_name("add-resource-available", OP_JS_RESOURCE_AVAILABLE)); + op_names.push_back(builtin_name("set-preemptable", OP_JS_JOB_PREEMPTABLE)); + } } @@ -201,3 +225,37 @@ bool jobshop_util::is_job(expr* e, unsigned& j) { return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true); } +bool jobshop_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end) { + if (!is_app_of(e, m_fid, OP_JS_RESOURCE_AVAILABLE)) return false; + res = to_app(e)->get_arg(0); + arith_util a(m); + rational r; + if (!a.is_numeral(to_app(e)->get_arg(1), r) || !r.is_unsigned()) return false; + loadpct = r.get_unsigned(); + if (!a.is_numeral(to_app(e)->get_arg(2), r) || !r.is_uint64()) return false; + start = r.get_uint64(); + if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false; + end = r.get_uint64(); + return true; +} + +bool jobshop_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end) { + if (!is_app_of(e, m_fid, OP_JS_JOB_RESOURCE)) return false; + job = to_app(e)->get_arg(0); + res = to_app(e)->get_arg(1); + arith_util a(m); + rational r; + if (!a.is_numeral(to_app(e)->get_arg(2), r) || !r.is_unsigned()) return false; + loadpct = r.get_unsigned(); + if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false; + capacity = r.get_uint64(); + if (!a.is_numeral(to_app(e)->get_arg(4), r) || !r.is_uint64()) return false; + end = r.get_uint64(); + return true; +} + +bool jobshop_util::is_set_preemptable(expr* e, expr *& job) { + if (!is_app_of(e, m_fid, OP_JS_JOB_PREEMPTABLE)) return false; + job = to_app(e)->get_arg(0); + return true; +} diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/jobshop_decl_plugin.h index f2cc7a849..f295c901f 100644 --- a/src/ast/jobshop_decl_plugin.h +++ b/src/ast/jobshop_decl_plugin.h @@ -79,6 +79,9 @@ enum js_op_kind { OP_JS_END, // end time of a job OP_JS_JOB2RESOURCE, // resource associated with job OP_JS_MODEL, // jobscheduler model + OP_JS_JOB_RESOURCE, + OP_JS_JOB_PREEMPTABLE, + OP_JS_RESOURCE_AVAILABLE, OP_AL_KV, // key-value pair OP_AL_LIST // tagged list }; @@ -133,6 +136,10 @@ public: app* mk_end(unsigned j); app* mk_job2resource(unsigned j); + bool is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end); + bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end); + bool is_set_preemptable(expr* e, expr *& job); + // alist features app* mk_kv(symbol const& key, rational const& r) { parameter ps[2] = { parameter(key), parameter(r) }; diff --git a/src/smt/smt_model_generator.cpp b/src/smt/smt_model_generator.cpp index a4b3029e2..c27845ff6 100644 --- a/src/smt/smt_model_generator.cpp +++ b/src/smt/smt_model_generator.cpp @@ -402,7 +402,10 @@ namespace smt { enode * n = m_context->get_enode(t); unsigned num_args = n->get_num_args(); func_decl * f = n->get_decl(); - if (num_args > 0 && n->get_cg() == n && include_func_interp(f)) { + if (num_args == 0 && include_func_interp(f)) { + m_model->register_decl(f, get_value(n)); + } + else if (num_args > 0 && n->get_cg() == n && include_func_interp(f)) { ptr_buffer args; expr * result = get_value(n); SASSERT(result); diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index c640c346a..c138b6012 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -48,6 +48,7 @@ Features: #include "smt/theory_jobscheduler.h" #include "smt/smt_context.h" #include "smt/smt_arith_value.h" +#include "smt/smt_model_generator.h" namespace smt { @@ -60,53 +61,28 @@ namespace smt { context & ctx = get_context(); if (ctx.e_internalized(term)) return true; + for (expr* arg : *term) { + if (!ctx.e_internalized(arg)) + ctx.internalize(arg, false); + } enode* e = ctx.mk_enode(term, false, false, true); - switch (static_cast(term->get_decl()->get_decl_kind())) { - case OP_JS_JOB: { - unsigned j = u.job2id(term); - app_ref start(u.mk_start(j), m); - app_ref end(u.mk_end(j), m); - app_ref res(u.mk_job2resource(j), m); - if (!ctx.e_internalized(start)) ctx.internalize(start, false); - if (!ctx.e_internalized(end)) ctx.internalize(end, false); - if (!ctx.e_internalized(res)) ctx.internalize(res, false); - theory_var v = mk_var(e); - SASSERT(m_var2index.size() == v); - m_var2index.push_back(j); - m_jobs.reserve(j + 1); - m_jobs[j].m_start = ctx.get_enode(start); - m_jobs[j].m_end = ctx.get_enode(end); - m_jobs[j].m_job2resource = ctx.get_enode(res); - ctx.attach_th_var(e, this, v); - break; - } - case OP_JS_RESOURCE: { - theory_var v = mk_var(e); - SASSERT(m_var2index.size() == v); - unsigned r = u.resource2id(term); - m_var2index.push_back(r); - ctx.attach_th_var(e, this, v); - break; - } - case OP_JS_START: - case OP_JS_END: - case OP_JS_JOB2RESOURCE: { - unsigned j = u.job2id(term); - app_ref job(u.mk_job(j), m); - if (!ctx.e_internalized(job)) ctx.internalize(job, false); - break; - } - } + theory_var v = mk_var(e); + ctx.attach_th_var(e, this, v); + ctx.mark_as_relevant(e); return true; } bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) { TRACE("csp", tout << mk_pp(atom, m) << "\n";); + context & ctx = get_context(); SASSERT(u.is_model(atom)); for (expr* arg : *atom) { + if (!ctx.e_internalized(arg)) ctx.internalize(arg, false); internalize_cmd(arg); } add_done(); + bool_var bv = ctx.mk_bool_var(atom); + ctx.set_var_theory(bv, get_id()); return true; } @@ -114,95 +90,18 @@ namespace smt { void theory_jobscheduler::internalize_cmd(expr* cmd) { symbol key, val; rational r; - if (u.is_kv(cmd, key, r) && key == ":set-preemptable" && r.is_unsigned()) { - set_preemptable(r.get_unsigned(), true); + expr* job, *resource; + unsigned j = 0, res = 0, cap = 0, loadpct = 100; + time_t start = 0, end = std::numeric_limits::max(), capacity = 0; + properties ps; + if (u.is_set_preemptable(cmd, job) && u.is_job(job, j)) { + set_preemptable(j, true); } - else if (u.is_alist(cmd, key) && key == ":add-job-resource") { - properties ps; - unsigned j = 0, res = 0, cap = 0, loadpct = 100; - time_t end = std::numeric_limits::max(); - bool has_j = false, has_res = false, has_cap = false, has_load = false, has_end = false; - for (expr* arg : *to_app(cmd)) { - if (u.is_kv(arg, key, r)) { - if (key == ":job" && r.is_unsigned()) { - j = r.get_unsigned(); - has_j = true; - } - else if (key == ":resource" && r.is_unsigned()) { - res = r.get_unsigned(); - has_res = true; - } - else if (key == ":capacity" && r.is_unsigned()) { - cap = r.get_unsigned(); - has_cap = true; - } - else if (key == ":loadpct" && r.is_unsigned()) { - loadpct = r.get_unsigned(); - has_load = true; - } - else if (key == ":end" && r.is_uint64()) { - end = r.get_uint64(); - has_end = true; - } - else { - unrecognized_argument(arg); - } - } - else if (u.is_alist(arg, key) && key == ":properties") { - // TBD - } - else { - unrecognized_argument(arg); - } - } - if (!has_j) invalid_argument(":job argument expected ", cmd); - if (!has_res) invalid_argument(":resource argument expected ", cmd); - if (!has_cap) invalid_argument(":capacity argument expected ", cmd); - if (!has_load) invalid_argument(":loadpct argument expected ", cmd); - if (!has_end) invalid_argument(":end argument expected ", cmd); - if (cap == 0) invalid_argument(":capacity should be positive ", cmd); - add_job_resource(j, res, cap, loadpct, end, ps); + else if (u.is_add_resource_available(cmd, resource, loadpct, start, end) && u.is_resource(resource, res)) { + add_resource_available(res, loadpct, start, end, ps); } - else if (u.is_alist(cmd, key) && key == ":add-resource-available") { - properties ps; - unsigned res = 0, loadpct = 100; - time_t start = 0, end = 0; - bool has_start = false, has_res = false, has_load = false, has_end = false; - - for (expr* arg : *to_app(cmd)) { - if (u.is_kv(arg, key, r)) { - if (key == ":resource" && r.is_unsigned()) { - res = r.get_unsigned(); - has_res = true; - } - else if (key == ":start" && r.is_uint64()) { - start = r.get_uint64(); - has_start = true; - } - else if (key == ":end" && r.is_uint64()) { - end = r.get_uint64(); - has_end = true; - } - else if (key == ":loadpct" && r.is_unsigned()) { - loadpct = r.get_unsigned(); - has_load = true; - } - else { - unrecognized_argument(arg); - } - } - else if (u.is_alist(arg, key) && key == ":properties") { - // TBD - } - else { - unrecognized_argument(arg); - } - } - if (!has_res) invalid_argument(":resource argument expected ", cmd); - if (!has_load) invalid_argument(":loadpct argument expected ", cmd); - if (!has_end) invalid_argument(":end argument expected ", cmd); - if (!has_start) invalid_argument(":start argument expected ", cmd); - add_resource_available(res, loadpct, start, end, ps); + else if (u.is_add_job_resource(cmd, job, resource, loadpct, capacity, end) && u.is_job(job, j) && u.is_resource(resource, res)) { + add_job_resource(j, res, loadpct, capacity, end, ps); } else { invalid_argument("command not recognized ", cmd); @@ -222,7 +121,7 @@ namespace smt { } final_check_status theory_jobscheduler::final_check_eh() { - + TRACE("csp", tout << "\n";); bool blocked = false; for (unsigned r = 0; r < m_resources.size(); ++r) { if (constrain_resource_energy(r)) { @@ -253,23 +152,23 @@ namespace smt { return ctx.get_literal(e); } - literal theory_jobscheduler::mk_ge_lit(expr* e, time_t t) { - return mk_literal(mk_ge(e, t)); + literal theory_jobscheduler::mk_ge(expr* e, time_t t) { + return mk_literal(mk_ge_expr(e, t)); } - expr* theory_jobscheduler::mk_ge(expr* e, time_t t) { + expr* theory_jobscheduler::mk_ge_expr(expr* e, time_t t) { return a.mk_ge(e, a.mk_int(rational(t, rational::ui64()))); } - expr* theory_jobscheduler::mk_ge(enode* e, time_t t) { + literal theory_jobscheduler::mk_ge(enode* e, time_t t) { return mk_ge(e->get_owner(), t); } - literal theory_jobscheduler::mk_le_lit(expr* e, time_t t) { - return mk_literal(mk_le(e, t)); + literal theory_jobscheduler::mk_le(expr* e, time_t t) { + return mk_literal(mk_le_expr(e, t)); } - expr* theory_jobscheduler::mk_le(expr* e, time_t t) { + expr* theory_jobscheduler::mk_le_expr(expr* e, time_t t) { return a.mk_le(e, a.mk_int(rational(t, rational::ui64()))); } @@ -280,10 +179,17 @@ namespace smt { return mk_literal(le); } - expr* theory_jobscheduler::mk_le(enode* e, time_t t) { + literal theory_jobscheduler::mk_le(enode* e, time_t t) { return mk_le(e->get_owner(), t); } + literal theory_jobscheduler::mk_eq_lit(expr * l, expr * r) { + literal lit = mk_eq(l, r, false); + get_context().mark_as_relevant(lit); + return lit; + } + + /** * iterator of job overlaps. */ @@ -307,7 +213,7 @@ namespace smt { ++s_idx; } // remove jobs that end before m_start. - while (e_idx < m_ends.size() && m_ends[s_idx].m_time < m_start) { + while (e_idx < m_ends.size() && m_ends[e_idx].m_time < m_start) { m_jobs.remove(m_ends[e_idx].m_job); ++e_idx; } @@ -330,7 +236,7 @@ namespace smt { if (clb > end(j)) { job_info const& ji = m_jobs[j]; - literal start_ge_lo = mk_literal(mk_ge(ji.m_start, slb)); + literal start_ge_lo = mk_ge(ji.m_start, slb); if (ctx.get_assignment(start_ge_lo) != l_true) { return; } @@ -339,7 +245,7 @@ namespace smt { return; } - literal end_ge_lo = mk_literal(mk_ge(ji.m_end, clb)); + literal end_ge_lo = mk_ge(ji.m_end, clb); // Initialization ensures that satisfiable states have completion time below end. VERIFY(clb <= get_job_resource(j, r).m_end); region& r = ctx.get_region(); @@ -358,10 +264,12 @@ namespace smt { bool theory_jobscheduler::constrain_end_time_interval(unsigned j, unsigned r) { unsigned idx1 = 0, idx2 = 0; time_t s = start(j); + TRACE("csp", tout << "job: " << j << " resource: " << r << " start: " << s << "\n";); if (!resource_available(r, s, idx1)) return false; vector& available = m_resources[r].m_available; time_t e = ect(j, r, s); - if (!resource_available(r, e, idx2)) return false; + TRACE("csp", tout << "job: " << j << " resource: " << r << " ect: " << e << "\n";); + if (!resource_available(r, e, idx2)) return false; // infeasible.. time_t start1 = available[idx1].m_start; time_t end1 = available[idx1].m_end; unsigned cap1 = available[idx1].m_loadpct; @@ -396,14 +304,15 @@ namespace smt { if (end(j) == start(j) + delta) { return false; } + TRACE("csp", tout << "job: " << j << " resource " << r << " t0: " << t0 << " t1: " << t1 << " delta: " << delta << "\n";); literal_vector lits; - expr* start_e = m_jobs[j].m_start->get_owner(); - expr* end_e = m_jobs[j].m_end->get_owner(); - lits.push_back(~mk_eq(m_jobs[j].m_job2resource->get_owner(), u.mk_resource(r), false)); - lits.push_back(~mk_ge_lit(start_e, t0)); - lits.push_back(~mk_le_lit(start_e, t1)); - expr_ref rhs(a.mk_add(start_e, a.mk_int(rational(delta, rational::ui64()))), m); - lits.push_back(mk_eq(end_e, rhs, false)); + enode* start_e = m_jobs[j].m_start; + enode* end_e = m_jobs[j].m_end; + lits.push_back(~mk_eq_lit(m_jobs[j].m_job2resource, m_resources[r].m_resource)); + lits.push_back(~mk_ge(start_e, t0)); + lits.push_back(~mk_le(start_e, t1)); + expr_ref rhs(a.mk_add(start_e->get_owner(), a.mk_int(rational(delta, rational::ui64()))), m); + lits.push_back(mk_eq_lit(end_e->get_owner(), rhs)); context& ctx = get_context(); ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_AUX_LEMMA, nullptr); return true; @@ -466,7 +375,7 @@ namespace smt { // resource(j) == r // m_jobs[j].m_start <= m_jobs[max_j].m_start; // m_jobs[max_j].m_start <= m_jobs[j].m_end; - lits.push_back(~mk_eq(u.mk_job2resource(j), u.mk_resource(r), false)); + lits.push_back(~mk_eq_lit(m_jobs[j].m_job2resource, m_resources[r].m_resource)); if (j != max_j) { lits.push_back(~mk_le(m_jobs[j].m_start, m_jobs[max_j].m_start)); lits.push_back(~mk_le(m_jobs[max_j].m_start, m_jobs[j].m_end)); @@ -478,6 +387,7 @@ namespace smt { } void theory_jobscheduler::propagate() { + return; for (unsigned j = 0; j < m_jobs.size(); ++j) { job_info const& ji = m_jobs[j]; unsigned r = resource(j); @@ -493,24 +403,23 @@ namespace smt { } std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { - return out << " r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_end << "\n"; + return out << "r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_end << "\n"; } std::ostream& theory_jobscheduler::display(std::ostream & out, job_info const& j) const { for (job_resource const& jr : j.m_resources) { - display(out, jr); + display(out << " ", jr); } return out; } std::ostream& theory_jobscheduler::display(std::ostream & out, res_available const& r) const { - out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%%\n"; - return out; + return out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%\n"; } std::ostream& theory_jobscheduler::display(std::ostream & out, res_info const& r) const { for (res_available const& ra : r.m_available) { - display(out, ra); + display(out << " ", ra); } return out; } @@ -529,15 +438,23 @@ namespace smt { } + bool theory_jobscheduler::include_func_interp(func_decl* f) { + return + f->get_decl_kind() == OP_JS_START || + f->get_decl_kind() == OP_JS_END || + f->get_decl_kind() == OP_JS_JOB2RESOURCE; + } + void theory_jobscheduler::init_model(model_generator & m) { } model_value_proc * theory_jobscheduler::mk_value(enode * n, model_generator & mg) { - return nullptr; + return alloc(expr_wrapper_proc, n->get_root()->get_owner()); } bool theory_jobscheduler::get_value(enode * n, expr_ref & r) { + std::cout << mk_pp(n->get_owner(), m) << "\n"; return false; } @@ -620,7 +537,7 @@ namespace smt { m_jobs[j].m_is_preemptable = is_preemptable; } - void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end, properties const& ps) { + void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned loadpct, uint64_t cap, time_t end, properties const& ps) { SASSERT(get_context().at_base_level()); SASSERT(1 <= loadpct && loadpct <= 100); SASSERT(0 < cap); @@ -630,6 +547,21 @@ namespace smt { if (ji.m_resource2index.contains(r)) { throw default_exception("resource already bound to job"); } + if (!ji.m_start) { + context& ctx = get_context(); + app_ref job(u.mk_job(j), m); + app_ref start(u.mk_start(j), m); + app_ref end(u.mk_end(j), m); + app_ref res(u.mk_job2resource(j), m); + if (!ctx.e_internalized(job)) ctx.internalize(job, false); + if (!ctx.e_internalized(start)) ctx.internalize(start, false); + if (!ctx.e_internalized(end)) ctx.internalize(end, false); + if (!ctx.e_internalized(res)) ctx.internalize(res, false); + ji.m_job = ctx.get_enode(job); + ji.m_start = ctx.get_enode(start); + ji.m_end = ctx.get_enode(end); + ji.m_job2resource = ctx.get_enode(res); + } ji.m_resource2index.insert(r, ji.m_resources.size()); ji.m_resources.push_back(job_resource(r, cap, loadpct, end, ps)); SASSERT(!m_resources[r].m_jobs.contains(j)); @@ -642,6 +574,12 @@ namespace smt { SASSERT(start <= end); m_resources.reserve(r + 1); res_info& ri = m_resources[r]; + if (!ri.m_resource) { + context& ctx = get_context(); + app_ref res(u.mk_resource(r), m); + if (!ctx.e_internalized(res)) ctx.internalize(res, false); + ri.m_resource = ctx.get_enode(res); + } ri.m_available.push_back(res_available(max_loadpct, start, end, ps)); } @@ -655,6 +593,7 @@ namespace smt { * Ensure that the availability slots for each resource is sorted by time. */ void theory_jobscheduler::add_done() { + TRACE("csp", tout << "add-done begin\n";); context & ctx = get_context(); // sort availability intervals @@ -666,18 +605,19 @@ namespace smt { } expr_ref fml(m); + literal lit, l1, l2, l3; for (unsigned j = 0; j < m_jobs.size(); ++j) { job_info const& ji = m_jobs[j]; - expr_ref_vector disj(m); + literal_vector disj; app_ref job(u.mk_job(j), m); if (ji.m_resources.empty()) { throw default_exception("every job should be associated with at least one resource"); } - // start(j) <= end(j) - fml = a.mk_le(ji.m_start->get_owner(), ji.m_end->get_owner()); - ctx.assert_expr(fml); + // start(j) <= end(j) + lit = mk_le(ji.m_start, ji.m_end); + ctx.mk_th_axiom(get_id(), 1, &lit); time_t start_lb = std::numeric_limits::max(); time_t end_ub = 0; @@ -685,48 +625,36 @@ namespace smt { // resource(j) = r => end(j) <= end(j, r) // resource(j) = r => start(j) <= lst(j, r, end(j, r)) unsigned r = jr.m_resource_id; - app_ref res(u.mk_resource(r), m); - expr_ref eq(m.mk_eq(job, res), m); - expr_ref imp(m.mk_implies(eq, mk_le(ji.m_end, jr.m_end)), m); - ctx.assert_expr(imp); - time_t t; - if (!lst(j, r, t)) { - imp = m.mk_implies(eq, mk_le(ji.m_start, t)); - } - else { - imp = m.mk_not(eq); - } - ctx.assert_expr(imp); - disj.push_back(eq); res_info const& ri = m_resources[r]; + enode* j2r = m_jobs[j].m_job2resource; + literal eq = mk_eq_lit(j2r, ri.m_resource); + assert_last_end_time(j, r, jr, eq); + assert_last_start_time(j, r, eq); + disj.push_back(eq); start_lb = std::min(start_lb, ri.m_available[0].m_start); end_ub = std::max(end_ub, ri.m_available.back().m_end); } // resource(j) = r1 || ... || resource(j) = r_n - expr_ref fml = mk_or(disj); - ctx.assert_expr(fml); + ctx.mk_th_axiom(get_id(), disj.size(), disj.c_ptr()); // start(j) >= start_lb - fml = mk_ge(ji.m_start, start_lb); - ctx.assert_expr(fml); + lit = mk_ge(ji.m_start, start_lb); + ctx.mk_th_axiom(get_id(), 1, &lit); // end(j) <= end_ub - fml = mk_le(ji.m_end, end_ub); - ctx.assert_expr(fml); + lit = mk_le(ji.m_end, end_ub); + ctx.mk_th_axiom(get_id(), 1, &lit); } for (unsigned r = 0; r < m_resources.size(); ++r) { res_info& ri = m_resources[r]; vector& available = ri.m_available; if (available.empty()) continue; - app_ref res(u.mk_resource(r), m); + enode* res = m_resources[r].m_resource; for (unsigned j : ri.m_jobs) { // resource(j) == r => start(j) >= available[0].m_start; - app_ref job(u.mk_job(j), m); - expr_ref eq(m.mk_eq(job, res), m); - expr_ref ge(mk_ge(m_jobs[j].m_start, available[0].m_start), m); - expr_ref fml(m.mk_implies(eq, ge), m); - ctx.assert_expr(fml); + enode* j2r = m_jobs[j].m_job2resource; + assert_first_start_time(j, r, mk_eq_lit(j2r, res)); } for (unsigned i = 0; i + 1 < available.size(); ++i) { if (available[i].m_end > available[i + 1].m_start) { @@ -735,26 +663,61 @@ namespace smt { for (unsigned j : ri.m_jobs) { // jobs start within an interval. // resource(j) == r => start(j) <= available[i].m_end || start(j) >= available[i + 1].m_start; - app_ref job(u.mk_job(j), m); - expr_ref eq(m.mk_eq(job, res), m); - expr_ref ge(mk_ge(m_jobs[j].m_start, available[i + 1].m_start), m); - expr_ref le(mk_le(m_jobs[j].m_start, available[i].m_end), m); - fml = m.mk_implies(eq, m.mk_or(le, ge)); - ctx.assert_expr(fml); + enode* j2r = m_jobs[j].m_job2resource; + literal eq = mk_eq_lit(j2r, res); + assert_job_not_in_gap(j, r, i, eq); // if job is not pre-emptable, start and end have to align within contiguous interval. // resource(j) == r => end(j) <= available[i].m_end || start(j) >= available[i + 1].m_start if (!m_jobs[j].m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { - le = mk_le(m_jobs[j].m_end, available[i].m_end); - ge = mk_ge(m_jobs[j].m_start, available[i+1].m_start); - fml = m.mk_implies(eq, m.mk_or(le, ge)); - ctx.assert_expr(fml); + assert_job_non_preemptable(j, r, i, eq); } } } } + TRACE("csp", tout << "add-done end\n";); } + void theory_jobscheduler::assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq) { + job_info const& ji = m_jobs[j]; + literal l2 = mk_le(ji.m_end, jr.m_end); + get_context().mk_th_axiom(get_id(), ~eq, l2); + } + + void theory_jobscheduler::assert_last_start_time(unsigned j, unsigned r, literal eq) { + context& ctx = get_context(); + time_t t; + if (lst(j, r, t)) { + ctx.mk_th_axiom(get_id(), ~eq, mk_le(m_jobs[j].m_start, t)); + } + else { + eq.neg(); + ctx.mk_th_axiom(get_id(), 1, &eq); + } + } + + void theory_jobscheduler::assert_first_start_time(unsigned j, unsigned r, literal eq) { + vector& available = m_resources[r].m_available; + literal l2 = mk_ge(m_jobs[j].m_start, available[0].m_start); + get_context().mk_th_axiom(get_id(), ~eq, l2); + } + + void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, literal eq) { + vector& available = m_resources[r].m_available; + literal l2 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); + literal l3 = mk_le(m_jobs[j].m_start, available[idx].m_end); + get_context().mk_th_axiom(get_id(), ~eq, l2, l3); + } + + void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, literal eq) { + vector& available = m_resources[r].m_available; + literal l2 = mk_le(m_jobs[j].m_end, available[idx].m_end); + literal l3 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); + get_context().mk_th_axiom(get_id(), ~eq, l2, l3); + } + + + /** * check that each job is run on some resource according to * requested capacity. @@ -836,6 +799,7 @@ namespace smt { time_t cap = jr.m_capacity; unsigned idx = 0; if (!resource_available(r, start, idx)) { + TRACE("csp", tout << "resource is not available for " << j << " " << r << "\n";); return std::numeric_limits::max(); } SASSERT(cap > 0); @@ -845,6 +809,7 @@ namespace smt { time_t end = available[idx].m_end; unsigned load_pct = available[idx].m_loadpct; time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); + TRACE("csp", tout << "delta: " << delta << " capacity: " << cap << " load " << load_pct << " jload: " << j_load_pct << " start: " << start << " end " << end << "\n";); if (delta > cap) { // // solve for end: @@ -870,29 +835,29 @@ namespace smt { time_t theory_jobscheduler::solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap) { SASSERT(load_pct > 0); SASSERT(job_load_pct > 0); - // cap = (load / job_load_pct) * (start - end + 1) + // cap = (load / job_load_pct) * (end - start + 1) // <=> - // start - end + 1 = (cap * job_load_pct) / load + // end - start + 1 = (cap * job_load_pct) / load // <=> - // end = start + 1 - (cap * job_load_pct) / load + // end = start - 1 + (cap * job_load_pct) / load // <=> - // end = (load * (start + 1) - cap * job_load_pct) / load + // end = (load * (start - 1) + cap * job_load_pct) / load unsigned load = std::min(load_pct, job_load_pct); - return (load * (start + 1) - cap * job_load_pct) / load; + return (load * (start - 1) + cap * job_load_pct) / load; } time_t theory_jobscheduler::solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap) { SASSERT(load_pct > 0); SASSERT(job_load_pct > 0); - // cap = (load / job_load_pct) * (start - end + 1) + // cap = (load / job_load_pct) * (end - start + 1) // <=> - // start - end + 1 = (cap * job_load_pct) / load + // end - start + 1 = (cap * job_load_pct) / load // <=> - // start = (cap * job_load_pct) / load + end - 1 + // start = end + 1 - (cap * job_load_pct) / load // <=> - // start = (load * (end - 1) + cap * job_load_pct) / load + // start = (load * (end + 1) - cap * job_load_pct) / load unsigned load = std::min(load_pct, job_load_pct); - return (load * (end - 1) + cap * job_load_pct) / load; + return (load * (end + 1) - cap * job_load_pct) / load; } time_t theory_jobscheduler::solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end) { @@ -905,23 +870,25 @@ namespace smt { * Compute last start time for job on resource r. */ bool theory_jobscheduler::lst(unsigned j, unsigned r, time_t& start) { + start = 0; job_resource const& jr = get_job_resource(j, r); vector& available = m_resources[r].m_available; unsigned j_load_pct = jr.m_loadpct; time_t cap = jr.m_capacity; for (unsigned idx = available.size(); idx-- > 0; ) { - time_t start = available[idx].m_start; + start = available[idx].m_start; time_t end = available[idx].m_end; unsigned load_pct = available[idx].m_loadpct; time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); if (delta > cap) { - start = solve_for_start(load_pct, j_load_pct, start, cap); + start = solve_for_start(load_pct, j_load_pct, end, cap); cap = 0; } else { cap -= delta; } if (cap == 0) { + std::cout << "start " << start << "\n"; return true; } } diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 7c5520548..b83f99251 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -36,11 +36,11 @@ namespace smt { struct job_resource { unsigned m_resource_id; // id of resource - unsigned m_capacity; // amount of resource to use + time_t m_capacity; // amount of resource to use unsigned m_loadpct; // assuming loadpct time_t m_end; // must run before properties m_properties; - job_resource(unsigned r, unsigned cap, unsigned loadpct, time_t end, properties const& ps): + job_resource(unsigned r, time_t cap, unsigned loadpct, time_t end, properties const& ps): m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_end(end), m_properties(ps) {} }; @@ -60,10 +60,11 @@ namespace smt { bool m_is_preemptable; // can job be pre-empted vector m_resources; // resources allowed to run job. u_map m_resource2index; // resource to index into vector + enode* m_job; enode* m_start; enode* m_end; enode* m_job2resource; - job_info(): m_is_preemptable(false), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr) {} + job_info(): m_is_preemptable(false), m_job(nullptr), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr) {} }; struct res_available { @@ -95,7 +96,6 @@ namespace smt { ast_manager& m; jobshop_util u; arith_util a; - unsigned_vector m_var2index; vector m_jobs; vector m_resources; @@ -133,6 +133,8 @@ namespace smt { void collect_statistics(::statistics & st) const override; + bool include_func_interp(func_decl* f) override; + void init_model(model_generator & m) override; model_value_proc * mk_value(enode * n, model_generator & mg) override; @@ -144,7 +146,7 @@ namespace smt { public: // set up job/resource global constraints void set_preemptable(unsigned j, bool is_preemptable); - void add_job_resource(unsigned j, unsigned r, unsigned cap, unsigned loadpct, time_t end, properties const& ps); + void add_job_resource(unsigned j, unsigned r, unsigned loadpct, time_t cap, time_t end, properties const& ps); void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps); void add_done(); @@ -184,6 +186,12 @@ namespace smt { bool constrain_end_time_interval(unsigned j, unsigned r); bool constrain_resource_energy(unsigned r); + void assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq); + void assert_last_start_time(unsigned j, unsigned r, literal eq); + void assert_first_start_time(unsigned j, unsigned r, literal eq); + void assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, literal eq); + void assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, literal eq); + void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job); class job_overlap { @@ -198,14 +206,16 @@ namespace smt { }; // term builders - literal mk_ge_lit(expr* e, time_t t); - expr* mk_ge(expr* e, time_t t); - expr* mk_ge(enode* e, time_t t); - literal mk_le_lit(expr* e, time_t t); - expr* mk_le(expr* e, time_t t); - expr* mk_le(enode* e, time_t t); + literal mk_ge(expr* e, time_t t); + expr* mk_ge_expr(expr* e, time_t t); + literal mk_ge(enode* e, time_t t); + literal mk_le(expr* e, time_t t); + expr* mk_le_expr(expr* e, time_t t); + literal mk_le(enode* e, time_t t); literal mk_le(enode* l, enode* r); literal mk_literal(expr* e); + literal mk_eq_lit(enode * l, enode * r) { return mk_eq_lit(l->get_owner(), r->get_owner()); } + literal mk_eq_lit(expr * l, expr * r); void internalize_cmd(expr* cmd); void unrecognized_argument(expr* arg) { invalid_argument("unrecognized argument ", arg); } From 502c07126640f7912b34c01c35cb7a06c37941ea Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 09:57:06 -0700 Subject: [PATCH 21/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/jobshop_decl_plugin.cpp | 49 ++++++++++++++++++--------------- src/ast/jobshop_decl_plugin.h | 35 ----------------------- src/smt/theory_jobscheduler.cpp | 7 +---- src/smt/theory_jobscheduler.h | 1 - 4 files changed, 28 insertions(+), 64 deletions(-) diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/jobshop_decl_plugin.cpp index 79a2cf177..0eb14aad3 100644 --- a/src/ast/jobshop_decl_plugin.cpp +++ b/src/ast/jobshop_decl_plugin.cpp @@ -57,23 +57,23 @@ func_decl * jobshop_decl_plugin::mk_func_decl( case OP_JS_JOB: check_arity(arity); check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job"), 0, (sort* const*)nullptr, m_job_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + return m_manager->mk_func_decl(symbol("job"), arity, domain, m_job_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); case OP_JS_RESOURCE: check_arity(arity); check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("resource"), 0, (sort* const*)nullptr, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + return m_manager->mk_func_decl(symbol("resource"), arity, domain, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); case OP_JS_START: - check_arity(arity); - check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job-start"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("start expects a job argument"); + if (num_parameters > 0) m_manager->raise_exception("no parameters"); + return m_manager->mk_func_decl(symbol("job-start"), arity, domain, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); case OP_JS_END: - check_arity(arity); - check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job-end"), 0, (sort* const*)nullptr, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("resource expects a job argument"); + if (num_parameters > 0) m_manager->raise_exception("no parameters"); + return m_manager->mk_func_decl(symbol("job-end"), arity, domain, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); case OP_JS_JOB2RESOURCE: - check_arity(arity); - check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job2resource"), 0, (sort* const*)nullptr, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("job2resource expects a job argument"); + if (num_parameters > 0) m_manager->raise_exception("no parameters"); + return m_manager->mk_func_decl(symbol("job2resource"), arity, domain, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); case OP_JS_MODEL: @@ -185,11 +185,13 @@ app* jobshop_util::mk_job(unsigned j) { } unsigned jobshop_util::job2id(expr* j) { - SASSERT(is_app_of(j, m_fid, OP_JS_JOB) || - is_app_of(j, m_fid, OP_JS_START) || + if (is_app_of(j, m_fid, OP_JS_JOB)) { + return to_app(j)->get_decl()->get_parameter(0).get_int(); + } + SASSERT(is_app_of(j, m_fid, OP_JS_START) || is_app_of(j, m_fid, OP_JS_END) || is_app_of(j, m_fid, OP_JS_JOB2RESOURCE)); - return to_app(j)->get_decl()->get_parameter(0).get_int(); + return job2id(to_app(j)->get_arg(0)); } app* jobshop_util::mk_resource(unsigned r) { @@ -203,18 +205,21 @@ unsigned jobshop_util::resource2id(expr* r) { } app* jobshop_util::mk_start(unsigned j) { - parameter p(j); - return m.mk_const(m.mk_func_decl(m_fid, OP_JS_START, 1, &p, 0, (sort*const*)nullptr, nullptr)); + app_ref job(mk_job(j), m); + sort* js = m.get_sort(job); + return m.mk_app(m.mk_func_decl(m_fid, OP_JS_START, 0, nullptr, 1, &js, nullptr), job); } - -app* jobshop_util::mk_end(unsigned j) { - parameter p(j); - return m.mk_const(m.mk_func_decl(m_fid, OP_JS_END, 1, &p, 0, (sort*const*)nullptr, nullptr)); + +app* jobshop_util::mk_end(unsigned j) { + app_ref job(mk_job(j), m); + sort* js = m.get_sort(job); + return m.mk_app(m.mk_func_decl(m_fid, OP_JS_END, 0, nullptr, 1, &js, nullptr), job); } app* jobshop_util::mk_job2resource(unsigned j) { - parameter p(j); - return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr)); + app_ref job(mk_job(j), m); + sort* js = m.get_sort(job); + return m.mk_app(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 0, nullptr, 1, &js, nullptr), job); } bool jobshop_util::is_resource(expr* e, unsigned& r) { diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/jobshop_decl_plugin.h index f295c901f..e7751436c 100644 --- a/src/ast/jobshop_decl_plugin.h +++ b/src/ast/jobshop_decl_plugin.h @@ -140,40 +140,6 @@ public: bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end); bool is_set_preemptable(expr* e, expr *& job); - // alist features - app* mk_kv(symbol const& key, rational const& r) { - parameter ps[2] = { parameter(key), parameter(r) }; - return m.mk_const(m.mk_func_decl(m_fid, OP_AL_KV, 2, ps, 0, (sort*const*)nullptr, nullptr)); - } - app* mk_kv(symbol const& key, symbol const& val) { - parameter ps[2] = { parameter(key), parameter(val) }; - return m.mk_const(m.mk_func_decl(m_fid, OP_AL_KV, 2, ps, 0, (sort*const*)nullptr, nullptr)); - } - app* mk_alist(symbol const& key, unsigned n, expr* const* args) { - parameter p(key); - return m.mk_app(m.mk_func_decl(m_fid, OP_AL_LIST, 1, &p, n, args, nullptr), n, args); - - } - bool is_kv(expr* e, symbol& key, rational& r) { - return - (is_app_of(e, m_fid, OP_AL_KV) && - to_app(e)->get_decl()->get_num_parameters() == 2 && - to_app(e)->get_decl()->get_parameter(1).is_rational() && - (r = to_app(e)->get_decl()->get_parameter(1).get_rational(), key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true)) || - (is_app_of(e, m_fid, OP_AL_KV) && - to_app(e)->get_decl()->get_num_parameters() == 2 && - to_app(e)->get_decl()->get_parameter(1).is_int() && - (r = rational(to_app(e)->get_decl()->get_parameter(1).get_int()), key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true)); - - - } - bool is_kv(expr* e, symbol& key, symbol& s) { - return is_app_of(e, m_fid, OP_AL_KV) && - to_app(e)->get_decl()->get_num_parameters() == 2 && - to_app(e)->get_decl()->get_parameter(1).is_symbol() && - (s = to_app(e)->get_decl()->get_parameter(1).get_symbol(), key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true); - } - bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } bool is_alist(expr* e) const { return is_app_of(e, m_fid, OP_AL_LIST); } bool is_alist(expr* e, symbol& key) const { @@ -183,5 +149,4 @@ public: (key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true); } - // app* mk_model(unsigned n, expr* const* alist); }; diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index c138b6012..918f349cd 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -240,7 +240,7 @@ namespace smt { if (ctx.get_assignment(start_ge_lo) != l_true) { return; } - enode_pair eq(ji.m_job2resource, resource2enode(r)); + enode_pair eq(ji.m_job2resource, m_resources[r].m_resource); if (eq.first->get_root() != eq.second->get_root()) { return; } @@ -528,10 +528,6 @@ namespace smt { return 0; } - enode* theory_jobscheduler::resource2enode(unsigned r) { - return get_context().get_enode(u.mk_resource(r)); - } - void theory_jobscheduler::set_preemptable(unsigned j, bool is_preemptable) { m_jobs.reserve(j + 1); m_jobs[j].m_is_preemptable = is_preemptable; @@ -610,7 +606,6 @@ namespace smt { for (unsigned j = 0; j < m_jobs.size(); ++j) { job_info const& ji = m_jobs[j]; literal_vector disj; - app_ref job(u.mk_job(j), m); if (ji.m_resources.empty()) { throw default_exception("every job should be associated with at least one resource"); } diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index b83f99251..778d8baa8 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -161,7 +161,6 @@ namespace smt { time_t get_up(expr* e); time_t get_value(expr* e); unsigned resource(unsigned j); // resource of job j - enode* resource2enode(unsigned r); // derived bounds time_t ect(unsigned j, unsigned r, time_t start); From a478f95999788b2266134bc64015fdf3f096e1db Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 10:56:52 -0700 Subject: [PATCH 22/65] remove debug assert Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index e889d0233..11a15492c 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1056,7 +1056,6 @@ sort* basic_decl_plugin::join(sort* s1, sort* s2) { return s2; } std::ostringstream buffer; - SASSERT(false); buffer << "Sorts " << mk_pp(s1, *m_manager) << " and " << mk_pp(s2, *m_manager) << " are incompatible"; throw ast_exception(buffer.str()); } From 2839f64f0db6744d14875456b5f78c36e978498d Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 11:05:55 -0700 Subject: [PATCH 23/65] rename to csp Signed-off-by: Nikolaj Bjorner --- src/ast/CMakeLists.txt | 2 +- ...op_decl_plugin.cpp => csp_decl_plugin.cpp} | 119 ++++++++++-------- ...obshop_decl_plugin.h => csp_decl_plugin.h} | 27 ++-- src/cmd_context/cmd_context.cpp | 4 +- src/smt/theory_jobscheduler.h | 4 +- 5 files changed, 79 insertions(+), 77 deletions(-) rename src/ast/{jobshop_decl_plugin.cpp => csp_decl_plugin.cpp} (66%) rename src/ast/{jobshop_decl_plugin.h => csp_decl_plugin.h} (86%) diff --git a/src/ast/CMakeLists.txt b/src/ast/CMakeLists.txt index 5340d4e7b..54e3c27e4 100644 --- a/src/ast/CMakeLists.txt +++ b/src/ast/CMakeLists.txt @@ -14,6 +14,7 @@ z3_add_component(ast ast_translation.cpp ast_util.cpp bv_decl_plugin.cpp + csp_decl_plugin.cpp datatype_decl_plugin.cpp decl_collector.cpp dl_decl_plugin.cpp @@ -30,7 +31,6 @@ z3_add_component(ast fpa_decl_plugin.cpp func_decl_dependencies.cpp has_free_vars.cpp - jobshop_decl_plugin.cpp macro_substitution.cpp num_occurs.cpp occurs.cpp diff --git a/src/ast/jobshop_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp similarity index 66% rename from src/ast/jobshop_decl_plugin.cpp rename to src/ast/csp_decl_plugin.cpp index 0eb14aad3..395677a4c 100644 --- a/src/ast/jobshop_decl_plugin.cpp +++ b/src/ast/csp_decl_plugin.cpp @@ -3,7 +3,7 @@ Copyright (c) 2018 Microsoft Corporation Module Name: - jobshop_decl_plugin.h + csp_decl_plugin.h Abstract: @@ -17,10 +17,10 @@ Revision History: --*/ -#include "ast/jobshop_decl_plugin.h" +#include "ast/csp_decl_plugin.h" #include "ast/arith_decl_plugin.h" -void jobshop_decl_plugin::set_manager(ast_manager* m, family_id fid) { +void csp_decl_plugin::set_manager(ast_manager* m, family_id fid) { decl_plugin::set_manager(m, fid); m_int_sort = m_manager->mk_sort(m_manager->mk_family_id("arith"), INT_SORT); m_alist_sort = m_manager->mk_sort(symbol("AList"), sort_info(m_family_id, ALIST_SORT)); @@ -32,14 +32,14 @@ void jobshop_decl_plugin::set_manager(ast_manager* m, family_id fid) { m_manager->inc_ref(m_alist_sort); } -void jobshop_decl_plugin::finalize() { +void csp_decl_plugin::finalize() { m_manager->dec_ref(m_alist_sort); m_manager->dec_ref(m_job_sort); m_manager->dec_ref(m_resource_sort); m_manager->dec_ref(m_int_sort); } -sort * jobshop_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { +sort * csp_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { if (num_parameters != 0) { m_manager->raise_exception("no parameters expected with job-shop sort"); } @@ -51,43 +51,47 @@ sort * jobshop_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parame } } -func_decl * jobshop_decl_plugin::mk_func_decl( +func_decl * csp_decl_plugin::mk_func_decl( decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort *) { + symbol name; + sort* rng = nullptr; switch (static_cast(k)) { case OP_JS_JOB: check_arity(arity); check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("job"), arity, domain, m_job_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("job"); + rng = m_job_sort; + break; case OP_JS_RESOURCE: check_arity(arity); check_index1(num_parameters, parameters); - return m_manager->mk_func_decl(symbol("resource"), arity, domain, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("resource"); + rng = m_resource_sort; + break; case OP_JS_START: if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("start expects a job argument"); if (num_parameters > 0) m_manager->raise_exception("no parameters"); - return m_manager->mk_func_decl(symbol("job-start"), arity, domain, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("job-start"); + rng = m_int_sort; + break; case OP_JS_END: if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("resource expects a job argument"); if (num_parameters > 0) m_manager->raise_exception("no parameters"); - return m_manager->mk_func_decl(symbol("job-end"), arity, domain, m_int_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("job-end"); + rng = m_int_sort; + break; case OP_JS_JOB2RESOURCE: if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("job2resource expects a job argument"); if (num_parameters > 0) m_manager->raise_exception("no parameters"); - return m_manager->mk_func_decl(symbol("job2resource"), arity, domain, m_resource_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); - - + name = symbol("job2resource"); + rng = m_resource_sort; + break; case OP_JS_MODEL: // has no parameters // all arguments are of sort alist - return m_manager->mk_func_decl(symbol("js-model"), arity, domain, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k, num_parameters, parameters)); - case OP_AL_KV: - // has two parameters, first is symbol - // has no arguments - return m_manager->mk_func_decl(symbol("kv"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); - case OP_AL_LIST: - // has no parameters - // all arguments are of sort alist - return m_manager->mk_func_decl(symbol("alist"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("js-model"); + rng = m_manager->mk_bool_sort(); + break; case OP_JS_JOB_RESOURCE: if (arity != 5) m_manager->raise_exception("add-job-resource expects 5 arguments"); if (domain[0] != m_job_sort) m_manager->raise_exception("first argument of add-job-resource expects should be a job"); @@ -95,43 +99,52 @@ func_decl * jobshop_decl_plugin::mk_func_decl( if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-job-resource expects should be an integer"); if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-job-resource expects should be an integer"); if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-job-resource expects should be an integer"); - return m_manager->mk_func_decl(symbol("add-job-resource"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("add-job-resource"); + rng = m_alist_sort; + break; case OP_JS_RESOURCE_AVAILABLE: if (arity != 4) m_manager->raise_exception("add-resource-available expects 4 arguments"); if (domain[0] != m_resource_sort) m_manager->raise_exception("first argument of add-resource-available expects should be a resource"); if (domain[1] != m_int_sort) m_manager->raise_exception("2nd argument of add-resource-available expects should be an integer"); if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-resource-available expects should be an integer"); if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-resource-available expects should be an integer"); - return m_manager->mk_func_decl(symbol("add-resource-available"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("add-resource-available"); + rng = m_alist_sort; + break; case OP_JS_JOB_PREEMPTABLE: if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("set-preemptable expects one argument, which is a job"); - return m_manager->mk_func_decl(symbol("set-preemptable"), arity, domain, m_alist_sort, func_decl_info(m_family_id, k, num_parameters, parameters)); + name = symbol("set-preemptable"); + rng = m_alist_sort; + break; default: - UNREACHABLE(); return nullptr; + UNREACHABLE(); + return nullptr; } + return m_manager->mk_func_decl(name, arity, domain, rng, func_decl_info(m_family_id, k, num_parameters, parameters)); + } -void jobshop_decl_plugin::check_arity(unsigned arity) { +void csp_decl_plugin::check_arity(unsigned arity) { if (arity > 0) - m_manager->raise_exception("jobshop variables use parameters only and take no arguments"); + m_manager->raise_exception("csp variables use parameters only and take no arguments"); } -void jobshop_decl_plugin::check_index1(unsigned num_parameters, parameter const* ps) { +void csp_decl_plugin::check_index1(unsigned num_parameters, parameter const* ps) { if (num_parameters != 1 || !ps[0].is_int()) - m_manager->raise_exception("jobshop variable expects a single integer parameter"); + m_manager->raise_exception("csp variable expects a single integer parameter"); } -void jobshop_decl_plugin::check_index2(unsigned num_parameters, parameter const* ps) { +void csp_decl_plugin::check_index2(unsigned num_parameters, parameter const* ps) { if (num_parameters != 2 || !ps[0].is_int() || !ps[1].is_int()) - m_manager->raise_exception("jobshop variable expects two integer parameters"); + m_manager->raise_exception("csp variable expects two integer parameters"); } -bool jobshop_decl_plugin::is_value(app * e) const { +bool csp_decl_plugin::is_value(app * e) const { return is_app_of(e, m_family_id, OP_JS_JOB) || is_app_of(e, m_family_id, OP_JS_RESOURCE); } -void jobshop_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { +void csp_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { if (logic == symbol("CSP")) { op_names.push_back(builtin_name("job", OP_JS_JOB)); op_names.push_back(builtin_name("resource", OP_JS_RESOURCE)); @@ -139,8 +152,6 @@ void jobshop_decl_plugin::get_op_names(svector & op_names, symbol op_names.push_back(builtin_name("job-end", OP_JS_END)); op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE)); op_names.push_back(builtin_name("js-model", OP_JS_MODEL)); - op_names.push_back(builtin_name("kv", OP_AL_KV)); - op_names.push_back(builtin_name("alist", OP_AL_LIST)); op_names.push_back(builtin_name("add-job-resource", OP_JS_JOB_RESOURCE)); op_names.push_back(builtin_name("add-resource-available", OP_JS_RESOURCE_AVAILABLE)); op_names.push_back(builtin_name("set-preemptable", OP_JS_JOB_PREEMPTABLE)); @@ -148,14 +159,14 @@ void jobshop_decl_plugin::get_op_names(svector & op_names, symbol } } -void jobshop_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { +void csp_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { if (logic == symbol("CSP")) { sort_names.push_back(builtin_name("Job", JOB_SORT)); sort_names.push_back(builtin_name("Resource", RESOURCE_SORT)); } } -expr * jobshop_decl_plugin::get_some_value(sort * s) { +expr * csp_decl_plugin::get_some_value(sort * s) { parameter p(0); if (is_sort_of(s, m_family_id, JOB_SORT)) return m_manager->mk_const(mk_func_decl(OP_JS_JOB, 1, &p, 0, nullptr, nullptr)); @@ -166,25 +177,25 @@ expr * jobshop_decl_plugin::get_some_value(sort * s) { } -jobshop_util::jobshop_util(ast_manager& m): m(m) { +csp_util::csp_util(ast_manager& m): m(m) { m_fid = m.mk_family_id("csp"); - m_plugin = static_cast(m.get_plugin(m_fid)); + m_plugin = static_cast(m.get_plugin(m_fid)); } -sort* jobshop_util::mk_job_sort() { +sort* csp_util::mk_job_sort() { return m_plugin->mk_job_sort(); } -sort* jobshop_util::mk_resource_sort() { +sort* csp_util::mk_resource_sort() { return m_plugin->mk_resource_sort(); } -app* jobshop_util::mk_job(unsigned j) { +app* csp_util::mk_job(unsigned j) { parameter p(j); return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB, 1, &p, 0, (sort*const*)nullptr, nullptr)); } -unsigned jobshop_util::job2id(expr* j) { +unsigned csp_util::job2id(expr* j) { if (is_app_of(j, m_fid, OP_JS_JOB)) { return to_app(j)->get_decl()->get_parameter(0).get_int(); } @@ -194,43 +205,43 @@ unsigned jobshop_util::job2id(expr* j) { return job2id(to_app(j)->get_arg(0)); } -app* jobshop_util::mk_resource(unsigned r) { +app* csp_util::mk_resource(unsigned r) { parameter p(r); return m.mk_const(m.mk_func_decl(m_fid, OP_JS_RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr)); } -unsigned jobshop_util::resource2id(expr* r) { +unsigned csp_util::resource2id(expr* r) { SASSERT(is_app_of(r, m_fid, OP_JS_RESOURCE)); return to_app(r)->get_decl()->get_parameter(0).get_int(); } -app* jobshop_util::mk_start(unsigned j) { +app* csp_util::mk_start(unsigned j) { app_ref job(mk_job(j), m); sort* js = m.get_sort(job); return m.mk_app(m.mk_func_decl(m_fid, OP_JS_START, 0, nullptr, 1, &js, nullptr), job); } -app* jobshop_util::mk_end(unsigned j) { +app* csp_util::mk_end(unsigned j) { app_ref job(mk_job(j), m); sort* js = m.get_sort(job); return m.mk_app(m.mk_func_decl(m_fid, OP_JS_END, 0, nullptr, 1, &js, nullptr), job); } -app* jobshop_util::mk_job2resource(unsigned j) { +app* csp_util::mk_job2resource(unsigned j) { app_ref job(mk_job(j), m); sort* js = m.get_sort(job); return m.mk_app(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 0, nullptr, 1, &js, nullptr), job); } -bool jobshop_util::is_resource(expr* e, unsigned& r) { +bool csp_util::is_resource(expr* e, unsigned& r) { return is_app_of(e, m_fid, OP_JS_RESOURCE) && (r = resource2id(e), true); } -bool jobshop_util::is_job(expr* e, unsigned& j) { +bool csp_util::is_job(expr* e, unsigned& j) { return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true); } -bool jobshop_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end) { +bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end) { if (!is_app_of(e, m_fid, OP_JS_RESOURCE_AVAILABLE)) return false; res = to_app(e)->get_arg(0); arith_util a(m); @@ -244,7 +255,7 @@ bool jobshop_util::is_add_resource_available(expr * e, expr *& res, unsigned& lo return true; } -bool jobshop_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end) { +bool csp_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end) { if (!is_app_of(e, m_fid, OP_JS_JOB_RESOURCE)) return false; job = to_app(e)->get_arg(0); res = to_app(e)->get_arg(1); @@ -259,7 +270,7 @@ bool jobshop_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsign return true; } -bool jobshop_util::is_set_preemptable(expr* e, expr *& job) { +bool csp_util::is_set_preemptable(expr* e, expr *& job) { if (!is_app_of(e, m_fid, OP_JS_JOB_PREEMPTABLE)) return false; job = to_app(e)->get_arg(0); return true; diff --git a/src/ast/jobshop_decl_plugin.h b/src/ast/csp_decl_plugin.h similarity index 86% rename from src/ast/jobshop_decl_plugin.h rename to src/ast/csp_decl_plugin.h index e7751436c..5443180a4 100644 --- a/src/ast/jobshop_decl_plugin.h +++ b/src/ast/csp_decl_plugin.h @@ -3,7 +3,7 @@ Copyright (c) 2018 Microsoft Corporation Module Name: - jobshop_decl_plugin.h + csp_decl_plugin.h Abstract: @@ -81,18 +81,16 @@ enum js_op_kind { OP_JS_MODEL, // jobscheduler model OP_JS_JOB_RESOURCE, OP_JS_JOB_PREEMPTABLE, - OP_JS_RESOURCE_AVAILABLE, - OP_AL_KV, // key-value pair - OP_AL_LIST // tagged list + OP_JS_RESOURCE_AVAILABLE }; -class jobshop_decl_plugin : public decl_plugin { +class csp_decl_plugin : public decl_plugin { public: - jobshop_decl_plugin() {} - ~jobshop_decl_plugin() override {} + csp_decl_plugin() {} + ~csp_decl_plugin() override {} void finalize() override; void set_manager(ast_manager* m, family_id fid) override; - decl_plugin * mk_fresh() override { return alloc(jobshop_decl_plugin); } + decl_plugin * mk_fresh() override { return alloc(csp_decl_plugin); } sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override; func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range) override; @@ -115,12 +113,12 @@ private: void check_index2(unsigned n, parameter const* ps); }; -class jobshop_util { +class csp_util { ast_manager& m; family_id m_fid; - jobshop_decl_plugin* m_plugin; + csp_decl_plugin* m_plugin; public: - jobshop_util(ast_manager& m); + csp_util(ast_manager& m); sort* mk_job_sort(); sort* mk_resource_sort(); @@ -141,12 +139,5 @@ public: bool is_set_preemptable(expr* e, expr *& job); bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } - bool is_alist(expr* e) const { return is_app_of(e, m_fid, OP_AL_LIST); } - bool is_alist(expr* e, symbol& key) const { - return is_alist(e) && - to_app(e)->get_decl()->get_num_parameters() == 1 && - to_app(e)->get_decl()->get_parameter(0).is_symbol() && - (key = to_app(e)->get_decl()->get_parameter(0).get_symbol(), true); - } }; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 8387722b9..da495968a 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -30,7 +30,7 @@ Notes: #include "ast/seq_decl_plugin.h" #include "ast/pb_decl_plugin.h" #include "ast/fpa_decl_plugin.h" -#include "ast/jobshop_decl_plugin.h" +#include "ast/csp_decl_plugin.h" #include "ast/ast_pp.h" #include "ast/rewriter/var_subst.h" #include "ast/pp.h" @@ -696,7 +696,7 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("pb"), alloc(pb_decl_plugin), logic_has_pb()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin), !has_logic()); - register_plugin(symbol("csp"), alloc(jobshop_decl_plugin), smt_logics::logic_is_csp(m_logic)); + register_plugin(symbol("csp"), alloc(csp_decl_plugin), smt_logics::logic_is_csp(m_logic)); } else { // the manager was created by an external module diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 778d8baa8..9031f9e78 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -21,7 +21,7 @@ Revision History: #pragma once; #include "util/uint_set.h" -#include "ast/jobshop_decl_plugin.h" +#include "ast/csp_decl_plugin.h" #include "ast/arith_decl_plugin.h" #include "smt/smt_theory.h" @@ -94,7 +94,7 @@ namespace smt { }; ast_manager& m; - jobshop_util u; + csp_util u; arith_util a; vector m_jobs; vector m_resources; From 40a79694ea8a80076b73a30cb2be865375d2a0b2 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 16:33:34 -0700 Subject: [PATCH 24/65] add job/resource axioms on demand Signed-off-by: Nikolaj Bjorner --- src/ast/csp_decl_plugin.cpp | 4 + src/ast/csp_decl_plugin.h | 1 + src/smt/theory_jobscheduler.cpp | 170 +++++++++++++++++++++----------- src/smt/theory_jobscheduler.h | 16 ++- 4 files changed, 133 insertions(+), 58 deletions(-) diff --git a/src/ast/csp_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp index 395677a4c..cfe67fd2d 100644 --- a/src/ast/csp_decl_plugin.cpp +++ b/src/ast/csp_decl_plugin.cpp @@ -241,6 +241,10 @@ bool csp_util::is_job(expr* e, unsigned& j) { return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true); } +bool csp_util::is_job2resource(expr* e, unsigned& j) { + return is_app_of(e, m_fid, OP_JS_JOB2RESOURCE) && (j = job2id(e), true); +} + bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end) { if (!is_app_of(e, m_fid, OP_JS_RESOURCE_AVAILABLE)) return false; res = to_app(e)->get_arg(0); diff --git a/src/ast/csp_decl_plugin.h b/src/ast/csp_decl_plugin.h index 5443180a4..b450f1fc2 100644 --- a/src/ast/csp_decl_plugin.h +++ b/src/ast/csp_decl_plugin.h @@ -124,6 +124,7 @@ public: app* mk_job(unsigned j); bool is_job(expr* e, unsigned& j); + bool is_job2resource(expr* e, unsigned& j); unsigned job2id(expr* j); app* mk_resource(unsigned r); diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 918f349cd..a415212c3 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -114,15 +114,57 @@ namespace smt { throw default_exception(strm.str()); } + void theory_jobscheduler::new_eq_eh(theory_var v1, theory_var v2) { + enode* e1 = get_enode(v1); + enode* e2 = get_enode(v2); + enode* root = e1->get_root(); + unsigned r; + if (u.is_resource(root->get_owner(), r)) { + enode* next = e1; + do { + unsigned j; + if (u.is_job2resource(next->get_owner(), j) && !m_jobs[j].m_is_bound) { + m_bound_jobs.push_back(j); + m_jobs[j].m_is_bound = true; + } + next = next->get_next(); + } + while (e1 != next); + } + } + + + void theory_jobscheduler::new_diseq_eh(theory_var v1, theory_var v2) { + + } + void theory_jobscheduler::push_scope_eh() { + scope s; + s.m_bound_jobs_lim = m_bound_jobs.size(); + s.m_bound_qhead = m_bound_qhead; + m_scopes.push_back(s); } void theory_jobscheduler::pop_scope_eh(unsigned num_scopes) { + unsigned new_lvl = m_scopes.size() - num_scopes; + scope const& s = m_scopes[new_lvl]; + for (unsigned i = s.m_bound_jobs_lim; i < m_bound_jobs.size(); ++i) { + unsigned j = m_bound_jobs[i]; + m_jobs[j].m_is_bound = false; + } + m_bound_jobs.shrink(s.m_bound_jobs_lim); + m_bound_qhead = s.m_bound_qhead; + m_scopes.shrink(new_lvl); } final_check_status theory_jobscheduler::final_check_eh() { TRACE("csp", tout << "\n";); bool blocked = false; + for (unsigned j = 0; j < m_jobs.size(); ++j) { + if (split_job2resource(j)) { + return FC_CONTINUE; + } + } for (unsigned r = 0; r < m_resources.size(); ++r) { if (constrain_resource_energy(r)) { blocked = true; @@ -139,7 +181,7 @@ namespace smt { } bool theory_jobscheduler::can_propagate() { - return false; + return m_bound_qhead < m_bound_jobs.size(); } literal theory_jobscheduler::mk_literal(expr * e) { @@ -387,19 +429,40 @@ namespace smt { } void theory_jobscheduler::propagate() { - return; - for (unsigned j = 0; j < m_jobs.size(); ++j) { + while (m_bound_qhead < m_bound_jobs.size()) { + unsigned j = m_bound_jobs[m_bound_qhead++]; + unsigned r = 0; job_info const& ji = m_jobs[j]; - unsigned r = resource(j); - propagate_end_time(j, r); + VERIFY(u.is_resource(ji.m_job2resource->get_root()->get_owner(), r)); + TRACE("csp", tout << "job: " << j << " resource: " << r << "\n";); + propagate_job2resource(j, r); } - for (unsigned r = 0; r < m_resources.size(); ++r) { - // TBD: check energy constraints on resources. + } + + void theory_jobscheduler::propagate_job2resource(unsigned j, unsigned r) { + job_info const& ji = m_jobs[j]; + res_info const& ri = m_resources[r]; + job_resource const& jr = get_job_resource(j, r); + literal eq = mk_eq_lit(ji.m_job2resource, ri.m_resource); + assert_last_end_time(j, r, jr, eq); + assert_last_start_time(j, r, eq); + assert_first_start_time(j, r, eq); + vector const& available = ri.m_available; + for (unsigned i = 0; i + 1 < available.size(); ++i) { + SASSERT(available[i].m_end < available[i + 1].m_start); + assert_job_not_in_gap(j, r, i, eq); + if (!ji.m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { + assert_job_non_preemptable(j, r, i, eq); + } } } theory_jobscheduler::theory_jobscheduler(ast_manager& m): - theory(m.get_family_id("csp")), m(m), u(m), a(m) { + theory(m.get_family_id("csp")), + m(m), + u(m), + a(m), + m_bound_qhead(0) { } std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { @@ -593,8 +656,7 @@ namespace smt { context & ctx = get_context(); // sort availability intervals - for (unsigned r = 0; r < m_resources.size(); ++r) { - res_info& ri = m_resources[r]; + for (res_info& ri : m_resources) { vector& available = ri.m_available; res_available::compare cmp; std::sort(available.begin(), available.end(), cmp); @@ -603,9 +665,7 @@ namespace smt { expr_ref fml(m); literal lit, l1, l2, l3; - for (unsigned j = 0; j < m_jobs.size(); ++j) { - job_info const& ji = m_jobs[j]; - literal_vector disj; + for (job_info const& ji : m_jobs) { if (ji.m_resources.empty()) { throw default_exception("every job should be associated with at least one resource"); } @@ -617,21 +677,15 @@ namespace smt { time_t start_lb = std::numeric_limits::max(); time_t end_ub = 0; for (job_resource const& jr : ji.m_resources) { - // resource(j) = r => end(j) <= end(j, r) - // resource(j) = r => start(j) <= lst(j, r, end(j, r)) unsigned r = jr.m_resource_id; res_info const& ri = m_resources[r]; - enode* j2r = m_jobs[j].m_job2resource; - literal eq = mk_eq_lit(j2r, ri.m_resource); - assert_last_end_time(j, r, jr, eq); - assert_last_start_time(j, r, eq); - disj.push_back(eq); + // literal eq = mk_eq_lit(ji.m_job2resource, ri.m_resource); + // disj.push_back(eq); start_lb = std::min(start_lb, ri.m_available[0].m_start); - end_ub = std::max(end_ub, ri.m_available.back().m_end); - + end_ub = std::max(end_ub, ri.m_available.back().m_end); } // resource(j) = r1 || ... || resource(j) = r_n - ctx.mk_th_axiom(get_id(), disj.size(), disj.c_ptr()); + // ctx.mk_th_axiom(get_id(), disj.size(), disj.c_ptr()); // start(j) >= start_lb lit = mk_ge(ji.m_start, start_lb); @@ -641,44 +695,18 @@ namespace smt { lit = mk_le(ji.m_end, end_ub); ctx.mk_th_axiom(get_id(), 1, &lit); } - for (unsigned r = 0; r < m_resources.size(); ++r) { - res_info& ri = m_resources[r]; - vector& available = ri.m_available; - if (available.empty()) continue; - enode* res = m_resources[r].m_resource; - for (unsigned j : ri.m_jobs) { - // resource(j) == r => start(j) >= available[0].m_start; - enode* j2r = m_jobs[j].m_job2resource; - assert_first_start_time(j, r, mk_eq_lit(j2r, res)); - } - for (unsigned i = 0; i + 1 < available.size(); ++i) { - if (available[i].m_end > available[i + 1].m_start) { - throw default_exception("availability intervals should be disjoint"); - } - for (unsigned j : ri.m_jobs) { - // jobs start within an interval. - // resource(j) == r => start(j) <= available[i].m_end || start(j) >= available[i + 1].m_start; - enode* j2r = m_jobs[j].m_job2resource; - literal eq = mk_eq_lit(j2r, res); - assert_job_not_in_gap(j, r, i, eq); - - // if job is not pre-emptable, start and end have to align within contiguous interval. - // resource(j) == r => end(j) <= available[i].m_end || start(j) >= available[i + 1].m_start - if (!m_jobs[j].m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { - assert_job_non_preemptable(j, r, i, eq); - } - } - } - } + TRACE("csp", tout << "add-done end\n";); } + // resource(j) = r => end(j) <= end(j, r) void theory_jobscheduler::assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq) { job_info const& ji = m_jobs[j]; literal l2 = mk_le(ji.m_end, jr.m_end); get_context().mk_th_axiom(get_id(), ~eq, l2); } + // resource(j) = r => start(j) <= lst(j, r, end(j, r)) void theory_jobscheduler::assert_last_start_time(unsigned j, unsigned r, literal eq) { context& ctx = get_context(); time_t t; @@ -691,12 +719,14 @@ namespace smt { } } + // resource(j) = r => start(j) >= avaialble[0].m_start void theory_jobscheduler::assert_first_start_time(unsigned j, unsigned r, literal eq) { vector& available = m_resources[r].m_available; literal l2 = mk_ge(m_jobs[j].m_start, available[0].m_start); get_context().mk_th_axiom(get_id(), ~eq, l2); } + // resource(j) = r => start[idx] <= end(j) || start(j) <= start[idx+1]; void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, literal eq) { vector& available = m_resources[r].m_available; literal l2 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); @@ -704,14 +734,45 @@ namespace smt { get_context().mk_th_axiom(get_id(), ~eq, l2, l3); } + // resource(j) = r => end(j) <= end[idx] || start(j) >= start[idx+1]; void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, literal eq) { vector& available = m_resources[r].m_available; literal l2 = mk_le(m_jobs[j].m_end, available[idx].m_end); literal l3 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); get_context().mk_th_axiom(get_id(), ~eq, l2, l3); - } + } - + bool theory_jobscheduler::split_job2resource(unsigned j) { + job_info const& ji = m_jobs[j]; + context& ctx = get_context(); + if (ji.m_is_bound) return false; + auto const& jrs = ji.m_resources; + for (job_resource const& jr : jrs) { + unsigned r = jr.m_resource_id; + res_info const& ri = m_resources[r]; + enode* e1 = ji.m_job2resource; + enode* e2 = ri.m_resource; + if (ctx.is_diseq(e1, e2)) + continue; + literal eq = mk_eq_lit(e1, e2); + if (ctx.get_assignment(eq) != l_false) { + ctx.mark_as_relevant(eq); + if (assume_eq(e1, e2)) { + return true; + } + } + } + literal_vector lits; + for (job_resource const& jr : jrs) { + unsigned r = jr.m_resource_id; + res_info const& ri = m_resources[r]; + enode* e1 = ji.m_job2resource; + enode* e2 = ri.m_resource; + lits.push_back(mk_eq_lit(e1, e2)); + } + ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); + return true; + } /** * check that each job is run on some resource according to @@ -883,7 +944,6 @@ namespace smt { cap -= delta; } if (cap == 0) { - std::cout << "start " << start << "\n"; return true; } } diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 9031f9e78..43f55f35a 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -64,7 +64,8 @@ namespace smt { enode* m_start; enode* m_end; enode* m_job2resource; - job_info(): m_is_preemptable(false), m_job(nullptr), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr) {} + bool m_is_bound; + job_info(): m_is_preemptable(false), m_job(nullptr), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr), m_is_bound(false) {} }; struct res_available { @@ -98,6 +99,13 @@ namespace smt { arith_util a; vector m_jobs; vector m_resources; + unsigned_vector m_bound_jobs; + unsigned m_bound_qhead; + struct scope { + unsigned m_bound_jobs_lim; + unsigned m_bound_qhead; + }; + svector m_scopes; protected: @@ -109,9 +117,9 @@ namespace smt { void assign_eh(bool_var v, bool is_true) override {} - void new_eq_eh(theory_var v1, theory_var v2) override {} + void new_eq_eh(theory_var v1, theory_var v2) override; - void new_diseq_eh(theory_var v1, theory_var v2) override {} + void new_diseq_eh(theory_var v1, theory_var v2) override; void push_scope_eh() override; @@ -180,10 +188,12 @@ namespace smt { // propagation void propagate_end_time(unsigned j, unsigned r); void propagate_resource_energy(unsigned r); + void propagate_job2resource(unsigned j, unsigned r); // final check constraints bool constrain_end_time_interval(unsigned j, unsigned r); bool constrain_resource_energy(unsigned r); + bool split_job2resource(unsigned j); void assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq); void assert_last_start_time(unsigned j, unsigned r, literal eq); From d67bfd78b971066f8052c458a3fabf7cf59224c3 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 21:15:55 -0700 Subject: [PATCH 25/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/csp_decl_plugin.cpp | 16 ++++++++++++++++ src/ast/csp_decl_plugin.h | 26 ++++++++++++++------------ src/smt/theory_jobscheduler.cpp | 9 +++++++-- src/smt/theory_jobscheduler.h | 3 ++- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/ast/csp_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp index cfe67fd2d..a6255d676 100644 --- a/src/ast/csp_decl_plugin.cpp +++ b/src/ast/csp_decl_plugin.cpp @@ -68,6 +68,11 @@ func_decl * csp_decl_plugin::mk_func_decl( name = symbol("resource"); rng = m_resource_sort; break; + case OP_JS_RESOURCE_MAKESPAN: + if (arity != 1 || domain[0] != m_resource_sort) m_manager->raise_exception("makespan expects a resource argument"); + name = symbol("makespan"); + rng = m_int_sort; + break; case OP_JS_START: if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("start expects a job argument"); if (num_parameters > 0) m_manager->raise_exception("no parameters"); @@ -148,6 +153,7 @@ void csp_decl_plugin::get_op_names(svector & op_names, symbol cons if (logic == symbol("CSP")) { op_names.push_back(builtin_name("job", OP_JS_JOB)); op_names.push_back(builtin_name("resource", OP_JS_RESOURCE)); + op_names.push_back(builtin_name("makespan", OP_JS_RESOURCE_MAKESPAN)); op_names.push_back(builtin_name("job-start", OP_JS_START)); op_names.push_back(builtin_name("job-end", OP_JS_END)); op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE)); @@ -233,10 +239,20 @@ app* csp_util::mk_job2resource(unsigned j) { return m.mk_app(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 0, nullptr, 1, &js, nullptr), job); } +app* csp_util::mk_makespan(unsigned r) { + app_ref resource(mk_resource(r), m); + sort* rs = m.get_sort(resource); + return m.mk_app(m.mk_func_decl(m_fid, OP_JS_RESOURCE_MAKESPAN, 0, nullptr, 1, &rs, nullptr), resource); +} + bool csp_util::is_resource(expr* e, unsigned& r) { return is_app_of(e, m_fid, OP_JS_RESOURCE) && (r = resource2id(e), true); } +bool csp_util::is_makespan(expr * e, unsigned& r) { + return is_app_of(e, m_fid, OP_JS_RESOURCE_MAKESPAN) && is_resource(to_app(e)->get_arg(0), r); +} + bool csp_util::is_job(expr* e, unsigned& j) { return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true); } diff --git a/src/ast/csp_decl_plugin.h b/src/ast/csp_decl_plugin.h index b450f1fc2..faaaf344c 100644 --- a/src/ast/csp_decl_plugin.h +++ b/src/ast/csp_decl_plugin.h @@ -75,13 +75,14 @@ enum js_sort_kind { enum js_op_kind { OP_JS_JOB, // value of type job OP_JS_RESOURCE, // value of type resource + OP_JS_RESOURCE_MAKESPAN, // makespan of resource: the minimal resource time required for assigned jobs. OP_JS_START, // start time of a job OP_JS_END, // end time of a job OP_JS_JOB2RESOURCE, // resource associated with job OP_JS_MODEL, // jobscheduler model - OP_JS_JOB_RESOURCE, - OP_JS_JOB_PREEMPTABLE, - OP_JS_RESOURCE_AVAILABLE + OP_JS_JOB_RESOURCE, // model declaration for job assignment to resource + OP_JS_JOB_PREEMPTABLE, // model declaration for whether job is pre-emptable + OP_JS_RESOURCE_AVAILABLE // model declaration for availability intervals of resource }; class csp_decl_plugin : public decl_plugin { @@ -116,29 +117,30 @@ private: class csp_util { ast_manager& m; family_id m_fid; - csp_decl_plugin* m_plugin; + csp_decl_plugin* m_plugin; public: csp_util(ast_manager& m); sort* mk_job_sort(); sort* mk_resource_sort(); app* mk_job(unsigned j); - bool is_job(expr* e, unsigned& j); - bool is_job2resource(expr* e, unsigned& j); - unsigned job2id(expr* j); - app* mk_resource(unsigned r); - bool is_resource(expr* e, unsigned& r); - unsigned resource2id(expr* r); - app* mk_start(unsigned j); app* mk_end(unsigned j); app* mk_job2resource(unsigned j); + app* mk_makespan(unsigned r); + bool is_job(expr* e, unsigned& j); + bool is_job2resource(expr* e, unsigned& j); + bool is_resource(expr* e, unsigned& r); + bool is_makespan(expr* e, unsigned& r); bool is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end); bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end); bool is_set_preemptable(expr* e, expr *& job); - bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } +private: + unsigned job2id(expr* j); + unsigned resource2id(expr* r); + }; diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index a415212c3..ce1b2a82b 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -37,6 +37,7 @@ Features: - try optimization based on arithmetic solver. - earliest start, latest start - constraint level + - add constraints gradually - resource groups - resource groups like a resource - resources bound to resource groups within time intervals @@ -638,6 +639,9 @@ namespace smt { app_ref res(u.mk_resource(r), m); if (!ctx.e_internalized(res)) ctx.internalize(res, false); ri.m_resource = ctx.get_enode(res); + app_ref ms(u.mk_makespan(r), m); + if (!ctx.e_internalized(ms)) ctx.internalize(ms, false); + ri.m_makespan = ctx.get_enode(ms); } ri.m_available.push_back(res_available(max_loadpct, start, end, ps)); } @@ -823,18 +827,19 @@ namespace smt { vector& available = m_resources[r].m_available; unsigned lo = 0, hi = available.size(), mid = hi / 2; while (lo < hi) { + SASSERT(lo <= mid && mid < hi); res_available const& ra = available[mid]; if (ra.m_start <= t && t <= ra.m_end) { idx = mid; return true; } else if (ra.m_start > t && mid > 0) { - hi = mid - 1; + hi = mid; mid = lo + (mid - lo) / 2; } else if (ra.m_end < t) { lo = mid + 1; - mid += (hi - mid) / 2; + mid += (hi - mid + 1) / 2; } else { break; diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 43f55f35a..f18299a12 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -91,7 +91,8 @@ namespace smt { vector m_available; // time intervals where resource is available time_t m_end; // can't run after enode* m_resource; - res_info(): m_end(std::numeric_limits::max()), m_resource(nullptr) {} + enode* m_makespan; + res_info(): m_end(std::numeric_limits::max()), m_resource(nullptr), m_makespan(nullptr) {} }; ast_manager& m; From 03bd010b0523c72883c83d033fe67e03a89555f8 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 14 Aug 2018 21:19:06 -0700 Subject: [PATCH 26/65] na Signed-off-by: Nikolaj Bjorner --- src/smt/theory_jobscheduler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index ce1b2a82b..0e8e828a0 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -839,7 +839,7 @@ namespace smt { } else if (ra.m_end < t) { lo = mid + 1; - mid += (hi - mid + 1) / 2; + mid += (hi - mid) / 2; } else { break; From fd5cfbe40248d44e3cfa99e9a319bfb4c0ce8c9b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 15 Aug 2018 10:38:23 -0700 Subject: [PATCH 27/65] na Signed-off-by: Nikolaj Bjorner --- src/smt/theory_jobscheduler.cpp | 70 +++++++++++++++++---------------- src/smt/theory_jobscheduler.h | 1 - 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 0e8e828a0..f27efd5a3 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -19,13 +19,9 @@ TODO: - arithmetic interface - propagation queue: - - register theory variables for catching when jobs are bound to resources - register bounds on start times to propagate energy constraints - more general registration mechanism for arithmetic theory. -- csp_cmds - - use predicates for add_ feature set? Closed world. - set up environment in one swoop. - - interact with opt +- interact with opt - jobs without resources - complain or add dummy resource? At which level. @@ -271,6 +267,9 @@ namespace smt { /** * r = resource(j) & start(j) >= slb => end(j) >= ect(j, r, slb) + * + * note: not used so far + * note: subsumed by constrain_end_time_interval used in final-check */ void theory_jobscheduler::propagate_end_time(unsigned j, unsigned r) { time_t slb = est(j); @@ -361,9 +360,6 @@ namespace smt { return true; } - void theory_jobscheduler::propagate_resource_energy(unsigned r) { - - } /** * Ensure that job overlaps don't exceed available energy @@ -613,7 +609,7 @@ namespace smt { app_ref start(u.mk_start(j), m); app_ref end(u.mk_end(j), m); app_ref res(u.mk_job2resource(j), m); - if (!ctx.e_internalized(job)) ctx.internalize(job, false); + if (!ctx.e_internalized(job)) ctx.internalize(job, false); if (!ctx.e_internalized(start)) ctx.internalize(start, false); if (!ctx.e_internalized(end)) ctx.internalize(end, false); if (!ctx.e_internalized(res)) ctx.internalize(res, false); @@ -648,12 +644,18 @@ namespace smt { /* * Initialze the state based on the set of jobs and resources added. - * For each job j, with possible resources r1, ..., r_n assert - * resource(j) = r_1 || resource(j) = r_2 || ... || resource(j) = r_n - * For each job and resource r with deadline end(j,r) assert - * resource(j) = r => end(j) <= end(j,r) - * * Ensure that the availability slots for each resource is sorted by time. + * + * For each resource j: + * est(j) <= start(j) <= end(j) <= lct(j) + * + * possible strengthenings: + * - start(j) <= lst(j) + * - start(j) + min_completion_time(j) <= end(j) + * - start(j) + max_completion_time(j) >= end(j) + * + * makespan constraints? + * */ void theory_jobscheduler::add_done() { TRACE("csp", tout << "add-done begin\n";); @@ -666,8 +668,7 @@ namespace smt { std::sort(available.begin(), available.end(), cmp); } - expr_ref fml(m); - literal lit, l1, l2, l3; + literal lit; for (job_info const& ji : m_jobs) { if (ji.m_resources.empty()) { @@ -683,13 +684,9 @@ namespace smt { for (job_resource const& jr : ji.m_resources) { unsigned r = jr.m_resource_id; res_info const& ri = m_resources[r]; - // literal eq = mk_eq_lit(ji.m_job2resource, ri.m_resource); - // disj.push_back(eq); start_lb = std::min(start_lb, ri.m_available[0].m_start); end_ub = std::max(end_ub, ri.m_available.back().m_end); } - // resource(j) = r1 || ... || resource(j) = r_n - // ctx.mk_th_axiom(get_id(), disj.size(), disj.c_ptr()); // start(j) >= start_lb lit = mk_ge(ji.m_start, start_lb); @@ -723,14 +720,14 @@ namespace smt { } } - // resource(j) = r => start(j) >= avaialble[0].m_start + // resource(j) = r => start(j) >= available[0].m_start void theory_jobscheduler::assert_first_start_time(unsigned j, unsigned r, literal eq) { vector& available = m_resources[r].m_available; literal l2 = mk_ge(m_jobs[j].m_start, available[0].m_start); get_context().mk_th_axiom(get_id(), ~eq, l2); } - // resource(j) = r => start[idx] <= end(j) || start(j) <= start[idx+1]; + // resource(j) = r => start(j) <= end[idx] || start[idx+1] <= start(j); void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, literal eq) { vector& available = m_resources[r].m_available; literal l2 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); @@ -738,7 +735,7 @@ namespace smt { get_context().mk_th_axiom(get_id(), ~eq, l2, l3); } - // resource(j) = r => end(j) <= end[idx] || start(j) >= start[idx+1]; + // resource(j) = r => end(j) <= end[idx] || start[idx+1] <= start(j); void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, literal eq) { vector& available = m_resources[r].m_available; literal l2 = mk_le(m_jobs[j].m_end, available[idx].m_end); @@ -746,6 +743,9 @@ namespace smt { get_context().mk_th_axiom(get_id(), ~eq, l2, l3); } + /** + * bind a job to one of the resources it can run on. + */ bool theory_jobscheduler::split_job2resource(unsigned j) { job_info const& ji = m_jobs[j]; context& ctx = get_context(); @@ -823,6 +823,9 @@ namespace smt { return ji.m_resources[ji.m_resource2index[r]]; } + /** + * find idx, if any, such that t is within the time interval of available[idx] + */ bool theory_jobscheduler::resource_available(unsigned r, time_t t, unsigned& idx) { vector& available = m_resources[r].m_available; unsigned lo = 0, hi = available.size(), mid = hi / 2; @@ -848,7 +851,6 @@ namespace smt { return false; } - /** * compute earliest completion time for job j on resource r starting at time start. */ @@ -870,16 +872,9 @@ namespace smt { time_t end = available[idx].m_end; unsigned load_pct = available[idx].m_loadpct; time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); - TRACE("csp", tout << "delta: " << delta << " capacity: " << cap << " load " << load_pct << " jload: " << j_load_pct << " start: " << start << " end " << end << "\n";); + TRACE("csp", tout << "delta: " << delta << " capacity: " << cap << " load " + << load_pct << " jload: " << j_load_pct << " start: " << start << " end " << end << "\n";); if (delta > cap) { - // - // solve for end: - // cap = load * (end - start + 1) - // <=> - // cap / load = (end - start + 1) - // <=> - // end = cap / load + start - 1 - // end = solve_for_end(load_pct, j_load_pct, start, cap); cap = 0; } @@ -893,6 +888,9 @@ namespace smt { return std::numeric_limits::max(); } + /** + * find end, such that cap = (load / job_load_pct) * (end - start + 1) + */ time_t theory_jobscheduler::solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap) { SASSERT(load_pct > 0); SASSERT(job_load_pct > 0); @@ -907,6 +905,9 @@ namespace smt { return (load * (start - 1) + cap * job_load_pct) / load; } + /** + * find start, such that cap = (load / job_load_pct) * (end - start + 1) + */ time_t theory_jobscheduler::solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap) { SASSERT(load_pct > 0); SASSERT(job_load_pct > 0); @@ -921,6 +922,9 @@ namespace smt { return (load * (end + 1) - cap * job_load_pct) / load; } + /** + * find cap, such that cap = (load / job_load_pct) * (end - start + 1) + */ time_t theory_jobscheduler::solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end) { SASSERT(job_load_pct > 0); unsigned load = std::min(load_pct, job_load_pct); diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index f18299a12..4fc5ba567 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -188,7 +188,6 @@ namespace smt { // propagation void propagate_end_time(unsigned j, unsigned r); - void propagate_resource_energy(unsigned r); void propagate_job2resource(unsigned j, unsigned r); // final check constraints From 72304616712b5366993218ca9c5812520342f7d9 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 20 Aug 2018 23:51:51 +0200 Subject: [PATCH 28/65] adding properities Signed-off-by: Nikolaj Bjorner --- src/api/api_datalog.cpp | 95 +++++++++++++++++++++++++- src/api/api_qe.cpp | 2 +- src/ast/csp_decl_plugin.cpp | 33 +++++++-- src/ast/csp_decl_plugin.h | 8 ++- src/smt/theory_jobscheduler.cpp | 116 +++++++++++++++++++++++++++----- src/smt/theory_jobscheduler.h | 10 ++- 6 files changed, 235 insertions(+), 29 deletions(-) diff --git a/src/api/api_datalog.cpp b/src/api/api_datalog.cpp index b0a4def55..61c0dc3a5 100644 --- a/src/api/api_datalog.cpp +++ b/src/api/api_datalog.cpp @@ -623,6 +623,99 @@ extern "C" { to_fixedpoint_ref(d)->ctx().add_constraint(to_expr(e), lvl); } -#include "api_datalog_spacer.inc" + Z3_lbool Z3_API Z3_fixedpoint_query_from_lvl (Z3_context c, Z3_fixedpoint d, Z3_ast q, unsigned lvl) { + Z3_TRY; + LOG_Z3_fixedpoint_query_from_lvl (c, d, q, lvl); + RESET_ERROR_CODE(); + lbool r = l_undef; + unsigned timeout = to_fixedpoint(d)->m_params.get_uint("timeout", mk_c(c)->get_timeout()); + unsigned rlimit = to_fixedpoint(d)->m_params.get_uint("rlimit", mk_c(c)->get_rlimit()); + { + scoped_rlimit _rlimit(mk_c(c)->m().limit(), rlimit); + cancel_eh eh(mk_c(c)->m().limit()); + api::context::set_interruptable si(*(mk_c(c)), eh); + scoped_timer timer(timeout, &eh); + try { + r = to_fixedpoint_ref(d)->ctx().query_from_lvl (to_expr(q), lvl); + } + catch (z3_exception& ex) { + mk_c(c)->handle_exception(ex); + r = l_undef; + } + to_fixedpoint_ref(d)->ctx().cleanup(); + } + return of_lbool(r); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_ast Z3_API Z3_fixedpoint_get_ground_sat_answer(Z3_context c, Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_get_ground_sat_answer(c, d); + RESET_ERROR_CODE(); + expr* e = to_fixedpoint_ref(d)->ctx().get_ground_sat_answer(); + mk_c(c)->save_ast_trail(e); + RETURN_Z3(of_expr(e)); + Z3_CATCH_RETURN(nullptr); + } + + Z3_ast_vector Z3_API Z3_fixedpoint_get_rules_along_trace( + Z3_context c, + Z3_fixedpoint d) + { + Z3_TRY; + LOG_Z3_fixedpoint_get_rules_along_trace(c, d); + ast_manager& m = mk_c(c)->m(); + Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, *mk_c(c), m); + mk_c(c)->save_object(v); + expr_ref_vector rules(m); + svector names; + + to_fixedpoint_ref(d)->ctx().get_rules_along_trace_as_formulas(rules, names); + for (unsigned i = 0; i < rules.size(); ++i) { + v->m_ast_vector.push_back(rules[i].get()); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(nullptr); + } + + Z3_symbol Z3_API Z3_fixedpoint_get_rule_names_along_trace( + Z3_context c, + Z3_fixedpoint d) + { + Z3_TRY; + LOG_Z3_fixedpoint_get_rule_names_along_trace(c, d); + ast_manager& m = mk_c(c)->m(); + Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, *mk_c(c), m); + mk_c(c)->save_object(v); + expr_ref_vector rules(m); + svector names; + std::stringstream ss; + + to_fixedpoint_ref(d)->ctx().get_rules_along_trace_as_formulas(rules, names); + for (unsigned i = 0; i < names.size(); ++i) { + ss << ";" << names[i].str(); + } + RETURN_Z3(of_symbol(symbol(ss.str().substr(1).c_str()))); + Z3_CATCH_RETURN(nullptr); + } + + void Z3_API Z3_fixedpoint_add_invariant(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred, Z3_ast property) { + Z3_TRY; + LOG_Z3_fixedpoint_add_invariant(c, d, pred, property); + RESET_ERROR_CODE(); + to_fixedpoint_ref(d)->ctx ().add_invariant(to_func_decl(pred), to_expr(property)); + Z3_CATCH; + } + + Z3_ast Z3_API Z3_fixedpoint_get_reachable(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred) { + Z3_TRY; + LOG_Z3_fixedpoint_get_reachable(c, d, pred); + RESET_ERROR_CODE(); + expr_ref r = to_fixedpoint_ref(d)->ctx().get_reachable(to_func_decl(pred)); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r.get())); + Z3_CATCH_RETURN(nullptr); + } + }; diff --git a/src/api/api_qe.cpp b/src/api/api_qe.cpp index 94e83144f..167a694aa 100644 --- a/src/api/api_qe.cpp +++ b/src/api/api_qe.cpp @@ -1,5 +1,5 @@ /*++ -Copyright (c) 2017 Arie Gurfinkel +Copyright (c) 2018 Microsoft Corporation Module Name: diff --git a/src/ast/csp_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp index a6255d676..27feadc6d 100644 --- a/src/ast/csp_decl_plugin.cpp +++ b/src/ast/csp_decl_plugin.cpp @@ -98,21 +98,23 @@ func_decl * csp_decl_plugin::mk_func_decl( rng = m_manager->mk_bool_sort(); break; case OP_JS_JOB_RESOURCE: - if (arity != 5) m_manager->raise_exception("add-job-resource expects 5 arguments"); + if (arity != 6) m_manager->raise_exception("add-job-resource expects 6 arguments"); if (domain[0] != m_job_sort) m_manager->raise_exception("first argument of add-job-resource expects should be a job"); if (domain[1] != m_resource_sort) m_manager->raise_exception("second argument of add-job-resource expects should be a resource"); if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-job-resource expects should be an integer"); if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-job-resource expects should be an integer"); if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-job-resource expects should be an integer"); + if (domain[5] != m_alist_sort) m_manager->raise_exception("6th argument of add-job-resource should be an a list of properties"); name = symbol("add-job-resource"); rng = m_alist_sort; break; case OP_JS_RESOURCE_AVAILABLE: - if (arity != 4) m_manager->raise_exception("add-resource-available expects 4 arguments"); + if (arity != 5) m_manager->raise_exception("add-resource-available expects 5 arguments"); if (domain[0] != m_resource_sort) m_manager->raise_exception("first argument of add-resource-available expects should be a resource"); if (domain[1] != m_int_sort) m_manager->raise_exception("2nd argument of add-resource-available expects should be an integer"); if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-resource-available expects should be an integer"); if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-resource-available expects should be an integer"); + if (domain[4] != m_alist_sort) m_manager->raise_exception("5th argument of add-resource-available should be an a list of properties"); name = symbol("add-resource-available"); rng = m_alist_sort; break; @@ -121,6 +123,14 @@ func_decl * csp_decl_plugin::mk_func_decl( name = symbol("set-preemptable"); rng = m_alist_sort; break; + case OP_JS_PROPERTIES: + if (arity != 0) m_manager->raise_exception("js-properties takes no arguments"); + for (unsigned i = 0; i < num_parameters; ++i) { + if (!parameters[i].is_symbol()) m_manager->raise_exception("js-properties expects a list of keyword parameters"); + } + name = symbol("js-properties"); + rng = m_alist_sort; + break; default: UNREACHABLE(); return nullptr; @@ -161,7 +171,7 @@ void csp_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("add-job-resource", OP_JS_JOB_RESOURCE)); op_names.push_back(builtin_name("add-resource-available", OP_JS_RESOURCE_AVAILABLE)); op_names.push_back(builtin_name("set-preemptable", OP_JS_JOB_PREEMPTABLE)); - + op_names.push_back(builtin_name("js-properties", OP_JS_PROPERTIES)); } } @@ -261,7 +271,7 @@ bool csp_util::is_job2resource(expr* e, unsigned& j) { return is_app_of(e, m_fid, OP_JS_JOB2RESOURCE) && (j = job2id(e), true); } -bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end) { +bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end, svector& properties) { if (!is_app_of(e, m_fid, OP_JS_RESOURCE_AVAILABLE)) return false; res = to_app(e)->get_arg(0); arith_util a(m); @@ -272,10 +282,11 @@ bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpc start = r.get_uint64(); if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false; end = r.get_uint64(); + if (!is_js_properties(to_app(e)->get_arg(4), properties)) return false; return true; } -bool csp_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end) { +bool csp_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end, svector& properties) { if (!is_app_of(e, m_fid, OP_JS_JOB_RESOURCE)) return false; job = to_app(e)->get_arg(0); res = to_app(e)->get_arg(1); @@ -287,6 +298,7 @@ bool csp_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& capacity = r.get_uint64(); if (!a.is_numeral(to_app(e)->get_arg(4), r) || !r.is_uint64()) return false; end = r.get_uint64(); + if (!is_js_properties(to_app(e)->get_arg(5), properties)) return false; return true; } @@ -295,3 +307,14 @@ bool csp_util::is_set_preemptable(expr* e, expr *& job) { job = to_app(e)->get_arg(0); return true; } + +bool csp_util::is_js_properties(expr* e, svector& properties) { + if (!is_app_of(e, m_fid, OP_JS_PROPERTIES)) return false; + unsigned sz = to_app(e)->get_decl()->get_num_parameters(); + for (unsigned i = 0; i < sz; ++i) { + properties.push_back(to_app(e)->get_decl()->get_parameter(i).get_symbol()); + } + return true; +} + + diff --git a/src/ast/csp_decl_plugin.h b/src/ast/csp_decl_plugin.h index faaaf344c..8d6ad56fa 100644 --- a/src/ast/csp_decl_plugin.h +++ b/src/ast/csp_decl_plugin.h @@ -82,7 +82,8 @@ enum js_op_kind { OP_JS_MODEL, // jobscheduler model OP_JS_JOB_RESOURCE, // model declaration for job assignment to resource OP_JS_JOB_PREEMPTABLE, // model declaration for whether job is pre-emptable - OP_JS_RESOURCE_AVAILABLE // model declaration for availability intervals of resource + OP_JS_RESOURCE_AVAILABLE, // model declaration for availability intervals of resource + OP_JS_PROPERTIES // model declaration of a set of properties. Each property is a keyword. }; class csp_decl_plugin : public decl_plugin { @@ -134,10 +135,11 @@ public: bool is_job2resource(expr* e, unsigned& j); bool is_resource(expr* e, unsigned& r); bool is_makespan(expr* e, unsigned& r); - bool is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end); - bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end); + bool is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, uint64_t& start, uint64_t& end, svector& properites); + bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end, svector& properites); bool is_set_preemptable(expr* e, expr *& job); bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } + bool is_js_properties(expr* e, svector& properties); private: unsigned job2id(expr* j); diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index f27efd5a3..48009be31 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -83,6 +83,12 @@ namespace smt { return true; } + struct symbol_cmp { + bool operator()(symbol const& s1, symbol const& s2) const { + return lt(s1, s2); + } + }; + // TBD: stronger parameter validation void theory_jobscheduler::internalize_cmd(expr* cmd) { symbol key, val; @@ -94,10 +100,12 @@ namespace smt { if (u.is_set_preemptable(cmd, job) && u.is_job(job, j)) { set_preemptable(j, true); } - else if (u.is_add_resource_available(cmd, resource, loadpct, start, end) && u.is_resource(resource, res)) { + else if (u.is_add_resource_available(cmd, resource, loadpct, start, end, ps) && u.is_resource(resource, res)) { + std::sort(ps.begin(), ps.end(), symbol_cmp()); add_resource_available(res, loadpct, start, end, ps); } - else if (u.is_add_job_resource(cmd, job, resource, loadpct, capacity, end) && u.is_job(job, j) && u.is_resource(resource, res)) { + else if (u.is_add_job_resource(cmd, job, resource, loadpct, capacity, end, ps) && u.is_job(job, j) && u.is_resource(resource, res)) { + std::sort(ps.begin(), ps.end(), symbol_cmp()); add_job_resource(j, res, loadpct, capacity, end, ps); } else { @@ -306,12 +314,15 @@ namespace smt { bool theory_jobscheduler::constrain_end_time_interval(unsigned j, unsigned r) { unsigned idx1 = 0, idx2 = 0; time_t s = start(j); + job_resource const& jr = get_job_resource(j, r); TRACE("csp", tout << "job: " << j << " resource: " << r << " start: " << s << "\n";); - if (!resource_available(r, s, idx1)) return false; vector& available = m_resources[r].m_available; + if (!resource_available(r, s, idx1)) return false; + if (!resource_available(jr, available[idx1])) return false; time_t e = ect(j, r, s); TRACE("csp", tout << "job: " << j << " resource: " << r << " ect: " << e << "\n";); if (!resource_available(r, e, idx2)) return false; // infeasible.. + if (!resource_available(jr, available[idx2])) return false; time_t start1 = available[idx1].m_start; time_t end1 = available[idx1].m_end; unsigned cap1 = available[idx1].m_loadpct; @@ -445,12 +456,19 @@ namespace smt { assert_last_start_time(j, r, eq); assert_first_start_time(j, r, eq); vector const& available = ri.m_available; - for (unsigned i = 0; i + 1 < available.size(); ++i) { - SASSERT(available[i].m_end < available[i + 1].m_start); - assert_job_not_in_gap(j, r, i, eq); + // TBD: needs to take properties into account + + unsigned i = 0; + if (!first_available(jr, ri, i)) return; + while (true) { + unsigned next = i + 1; + if (!first_available(jr, ri, next)) return; + SASSERT(available[i].m_end < available[next].m_start); + assert_job_not_in_gap(j, r, i, next, eq); if (!ji.m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { - assert_job_non_preemptable(j, r, i, eq); + assert_job_non_preemptable(j, r, i, next, eq); } + i = next; } } @@ -463,7 +481,8 @@ namespace smt { } std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { - return out << "r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_end << "\n"; + return out << "r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_end; + for (auto const& s : jr.m_properties) out << " " << s; out << "\n"; } std::ostream& theory_jobscheduler::display(std::ostream & out, job_info const& j) const { @@ -474,7 +493,8 @@ namespace smt { } std::ostream& theory_jobscheduler::display(std::ostream & out, res_available const& r) const { - return out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%\n"; + return out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%"; + for (auto const& s : r.m_properties) out << " " << s; out << "\n"; } std::ostream& theory_jobscheduler::display(std::ostream & out, res_info const& r) const { @@ -624,6 +644,7 @@ namespace smt { m_resources[r].m_jobs.push_back(j); } + void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps) { SASSERT(get_context().at_base_level()); SASSERT(1 <= max_loadpct && max_loadpct <= 100); @@ -684,8 +705,15 @@ namespace smt { for (job_resource const& jr : ji.m_resources) { unsigned r = jr.m_resource_id; res_info const& ri = m_resources[r]; - start_lb = std::min(start_lb, ri.m_available[0].m_start); - end_ub = std::max(end_ub, ri.m_available.back().m_end); + if (ri.m_available.empty()) continue; + unsigned idx = 0; + if (first_available(jr, ri, idx)) { + start_lb = std::min(start_lb, ri.m_available[idx].m_start); + } + idx = ri.m_available.size(); + if (last_available(jr, ri, idx)) { + end_ub = std::max(end_ub, ri.m_available[idx].m_end); + } } // start(j) >= start_lb @@ -722,24 +750,31 @@ namespace smt { // resource(j) = r => start(j) >= available[0].m_start void theory_jobscheduler::assert_first_start_time(unsigned j, unsigned r, literal eq) { + job_resource const& jr = get_job_resource(j, r); + unsigned idx = 0; + if (!first_available(jr, m_resources[r], idx)) return; vector& available = m_resources[r].m_available; - literal l2 = mk_ge(m_jobs[j].m_start, available[0].m_start); + literal l2 = mk_ge(m_jobs[j].m_start, available[idx].m_start); get_context().mk_th_axiom(get_id(), ~eq, l2); } // resource(j) = r => start(j) <= end[idx] || start[idx+1] <= start(j); - void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, literal eq) { + void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq) { + job_resource const& jr = get_job_resource(j, r); vector& available = m_resources[r].m_available; - literal l2 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); + SASSERT(resource_available(jr, available[idx])); + literal l2 = mk_ge(m_jobs[j].m_start, available[idx1].m_start); literal l3 = mk_le(m_jobs[j].m_start, available[idx].m_end); get_context().mk_th_axiom(get_id(), ~eq, l2, l3); } // resource(j) = r => end(j) <= end[idx] || start[idx+1] <= start(j); - void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, literal eq) { + void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq) { vector& available = m_resources[r].m_available; + job_resource const& jr = get_job_resource(j, r); + SASSERT(resource_available(jr, available[idx])); literal l2 = mk_le(m_jobs[j].m_end, available[idx].m_end); - literal l3 = mk_ge(m_jobs[j].m_start, available[idx + 1].m_start); + literal l3 = mk_ge(m_jobs[j].m_start, available[idx1].m_start); get_context().mk_th_axiom(get_id(), ~eq, l2, l3); } @@ -868,6 +903,7 @@ namespace smt { SASSERT(cap > 0); for (; idx < available.size(); ++idx) { + if (!resource_available(jr, available[idx])) continue; start = std::max(start, available[idx].m_start); time_t end = available[idx].m_end; unsigned load_pct = available[idx].m_loadpct; @@ -941,6 +977,7 @@ namespace smt { unsigned j_load_pct = jr.m_loadpct; time_t cap = jr.m_capacity; for (unsigned idx = available.size(); idx-- > 0; ) { + if (!resource_available(jr, available[idx])) continue; start = available[idx].m_start; time_t end = available[idx].m_end; unsigned load_pct = available[idx].m_loadpct; @@ -958,6 +995,53 @@ namespace smt { } return false; } + + /** + * \brief check that job properties is a subset of resource properties. + * It assumes that both vectors are sorted. + */ + + bool theory_jobscheduler::resource_available(job_resource const& jr, res_available const& ra) const { + auto const& jps = jr.m_properties; + auto const& rps = ra.m_properties; + if (jps.size() > rps.size()) return false; + unsigned j = 0, i = 0; + for (; i < jps.size() && j < rps.size(); ) { + if (jps[i] == rps[j]) { + ++i; ++j; + } + else if (lt(rps[j], jps[i])) { + ++j; + } + else { + break; + } + } + return i == jps.size(); + } + + /** + * \brief minimal current resource available for job resource, includes idx. + */ + bool theory_jobscheduler::first_available(job_resource const& jr, res_info const& ri, unsigned& idx) const { + for (; idx < ri.m_available.size(); ++idx) { + if (resource_available(jr, ri.m_available[idx])) + return true; + } + return false; + } + + /** + * \brief maximal previous resource available for job resource, excludes idx. + */ + bool theory_jobscheduler::last_available(job_resource const& jr, res_info const& ri, unsigned& idx) const { + while (idx-- > 0) { + if (resource_available(jr, ri.m_available[idx])) + return true; + } + return false; + } + }; diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h index 4fc5ba567..59c3b975d 100644 --- a/src/smt/theory_jobscheduler.h +++ b/src/smt/theory_jobscheduler.h @@ -31,7 +31,7 @@ namespace smt { class theory_jobscheduler : public theory { public: - typedef map properties; + typedef svector properties; protected: struct job_resource { @@ -175,6 +175,10 @@ namespace smt { time_t ect(unsigned j, unsigned r, time_t start); bool lst(unsigned j, unsigned r, time_t& t); + bool resource_available(job_resource const& jr, res_available const& ra) const; + bool first_available(job_resource const& jr, res_info const& ri, unsigned& idx) const; + bool last_available(job_resource const& jr, res_info const& ri, unsigned& idx) const; + time_t solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap); time_t solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap); time_t solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end); @@ -198,8 +202,8 @@ namespace smt { void assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq); void assert_last_start_time(unsigned j, unsigned r, literal eq); void assert_first_start_time(unsigned j, unsigned r, literal eq); - void assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, literal eq); - void assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, literal eq); + void assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq); + void assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq); void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job); From 43807a7edc7fba4cd785470dfe565f797d096553 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 31 Aug 2018 20:25:49 -0500 Subject: [PATCH 29/65] adding roundingSat strategy Signed-off-by: Nikolaj Bjorner --- src/sat/ba_solver.cpp | 497 +++++++++++++++++++++++--------- src/sat/ba_solver.h | 27 +- src/smt/theory_jobscheduler.cpp | 9 +- 3 files changed, 401 insertions(+), 132 deletions(-) diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index 4226b9791..c86ea3317 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -1009,21 +1009,6 @@ namespace sat { // --------------------------- // conflict resolution - void ba_solver::normalize_active_coeffs() { - reset_active_var_set(); - unsigned i = 0, j = 0, sz = m_active_vars.size(); - for (; i < sz; ++i) { - bool_var v = m_active_vars[i]; - if (!m_active_var_set.contains(v) && get_coeff(v) != 0) { - m_active_var_set.insert(v); - if (j != i) { - m_active_vars[j] = m_active_vars[i]; - } - ++j; - } - } - m_active_vars.shrink(j); - } void ba_solver::inc_coeff(literal l, unsigned offset) { SASSERT(offset > 0); @@ -1066,44 +1051,46 @@ namespace sat { return m_coeffs.get(v, 0); } + uint64_t ba_solver::get_coeff(literal lit) const { + int64_t c1 = get_coeff(lit.var()); + SASSERT(c1 < 0 == lit.sign()); + uint64_t c = static_cast(std::abs(c1)); + m_overflow |= c != c1; + return c; + } + + void ba_solver::get_coeff(bool_var v, literal& l, unsigned& c) { + int64_t c1 = get_coeff(v); + l = literal(v, c1 < 0); + c1 = std::abs(c1); + c = static_cast(c1); + m_overflow |= c != c1; + } + unsigned ba_solver::get_abs_coeff(bool_var v) const { - int64_t c = get_coeff(v); - if (c < INT_MIN+1 || c > UINT_MAX) { - m_overflow = true; - return UINT_MAX; - } - return static_cast(std::abs(c)); + int64_t c1 = std::abs(get_coeff(v)); + unsigned c = static_cast(c1); + m_overflow |= c != c1; + return c; } int ba_solver::get_int_coeff(bool_var v) const { - int64_t c = m_coeffs.get(v, 0); - if (c < INT_MIN || c > INT_MAX) { - m_overflow = true; - return 0; - } - return static_cast(c); + int64_t c1 = m_coeffs.get(v, 0); + int c = static_cast(c1); + m_overflow |= c != c1; + return c; } void ba_solver::inc_bound(int64_t i) { - if (i < INT_MIN || i > INT_MAX) { - m_overflow = true; - return; - } int64_t new_bound = m_bound; new_bound += i; - if (new_bound < 0) { - m_overflow = true; - } - else if (new_bound > UINT_MAX) { - m_overflow = true; - } - else { - m_bound = static_cast(new_bound); - } + unsigned nb = static_cast(new_bound); + m_overflow |= new_bound < 0 || nb != new_bound; + m_bound = nb; } void ba_solver::reset_coeffs() { - for (unsigned i = 0; i < m_active_vars.size(); ++i) { + for (unsigned i = m_active_vars.size(); i-- > 0; ) { m_coeffs[m_active_vars[i]] = 0; } m_active_vars.reset(); @@ -1115,7 +1102,47 @@ namespace sat { // #define DEBUG_CODE(_x_) _x_ - lbool ba_solver::resolve_conflict() { + void ba_solver::bail_resolve_conflict(unsigned idx) { + m_overflow = false; + literal_vector const& lits = s().m_trail; + while (m_num_marks > 0) { + bool_var v = lits[idx].var(); + if (s().is_marked(v)) { + s().reset_mark(v); + --m_num_marks; + } + if (idx == 0 && !_debug_conflict) { + _debug_conflict = true; + _debug_var2position.reserve(s().num_vars()); + for (unsigned i = 0; i < lits.size(); ++i) { + _debug_var2position[lits[i].var()] = i; + } + IF_VERBOSE(0, + active2pb(m_A); + uint64_t c = 0; + for (uint64_t c1 : m_A.m_coeffs) c += c1; + verbose_stream() << "sum of coefficients: " << c << "\n"; + display(verbose_stream(), m_A, true); + verbose_stream() << "conflicting literal: " << s().m_not_l << "\n";); + + for (literal l : lits) { + if (s().is_marked(l.var())) { + IF_VERBOSE(0, verbose_stream() << "missing mark: " << l << "\n";); + s().reset_mark(l.var()); + } + } + m_num_marks = 0; + resolve_conflict(); + } + --idx; + } + } + + lbool ba_solver::resolve_conflict() { +#if 1 + return resolve_conflict_rs(); +#endif + if (0 == m_num_propagations_since_pop) { return l_undef; } @@ -1256,7 +1283,6 @@ namespace sat { process_next_resolvent: // find the next marked variable in the assignment stack - // bool_var v; while (true) { consequent = lits[idx]; @@ -1290,7 +1316,6 @@ namespace sat { DEBUG_CODE(for (bool_var i = 0; i < static_cast(s().num_vars()); ++i) SASSERT(!s().is_marked(i));); SASSERT(validate_lemma()); - normalize_active_coeffs(); if (!create_asserting_lemma()) { goto bail_out; @@ -1315,43 +1340,243 @@ namespace sat { return l_true; bail_out: + bail_resolve_conflict(idx); + return l_undef; + } - m_overflow = false; + uint64_t ba_solver::ineq::coeff(literal l) const { + bool_var v = l.var(); + for (unsigned i = size(); i-- > 0; ) { + if (lit(i).var() == v) return coeff(i); + } + UNREACHABLE(); + return 0; + } + + void ba_solver::ineq::divide(uint64_t c) { + if (c == 1) return; + for (unsigned i = size(); i-- > 0; ) { + m_coeffs[i] = (m_coeffs[i] + c - 1) / c; + } + m_k = (m_k + c - 1) / c; + } + + /** + * Remove literal at position i, subtract coefficient from bound. + */ + void ba_solver::ineq::weaken(unsigned i) { + uint64_t ci = coeff(i); + SASSERT(m_k >= ci); + m_k -= ci; + m_lits[i] = m_lits.back(); + m_coeffs[i] = m_coeffs.back(); + m_lits.pop_back(); + m_coeffs.pop_back(); + } + + /** + * Round coefficient of inequality to 1. + */ + void ba_solver::round_to_one(ineq& ineq, literal lit) { + uint64_t c = ineq.coeff(lit); + if (c == 1) return; + unsigned sz = ineq.size(); + for (unsigned i = 0; i < sz; ++i) { + uint64_t ci = ineq.coeff(i); + if (ci % c != 0 && !is_false(ineq.lit(i))) { + ineq.weaken(i); + --i; + --sz; + } + } + ineq.divide(c); + } + + void ba_solver::round_to_one(literal lit) { + uint64_t c = get_coeff(lit); + if (c == 1) return; + for (bool_var v : m_active_vars) { + literal l; + unsigned ci; + get_coeff(v, l, ci); + if (ci > 0 && ci % c != 0 && !is_false(l)) { + m_coeffs[v] = 0; + } + } + divide(c); + } + + void ba_solver::divide(uint64_t c) { + SASSERT(c != 0); + if (c == 1) return; + reset_active_var_set(); + unsigned j = 0, sz = m_active_vars.size(); + for (unsigned i = 0; i < sz; ++i) { + bool_var v = m_active_vars[i]; + int ci = get_int_coeff(v); + if (m_active_var_set.contains(v) || ci == 0) continue; + m_active_var_set.insert(v); + if (ci > 0) { + m_coeffs[v] = (ci + c - 1) / c; + } + else { + m_coeffs[v] = -static_cast((-ci + c - 1) / c); + } + m_active_vars[j++] = v; + } + m_active_vars.shrink(j); + if (m_bound % c != 0) { + ++m_stats.m_num_cut; + m_bound = static_cast((m_bound + c - 1) / c); + } + } + + void ba_solver::resolve_on(literal consequent) { + round_to_one(consequent); + m_coeffs[consequent.var()] = 0; + } + + void ba_solver::resolve_with(ineq const& ineq) { + TRACE("ba", display(tout, ineq, true);); + inc_bound(1 + ineq.m_k); + for (unsigned i = ineq.size(); i-- > 0; ) { + literal l = ineq.lit(i); + inc_coeff(l, static_cast(ineq.coeff(i))); + } + } + + void ba_solver::reset_marks(unsigned idx) { while (m_num_marks > 0) { - bool_var v = lits[idx].var(); + SASSERT(idx > 0); + bool_var v = s().m_trail[idx].var(); if (s().is_marked(v)) { s().reset_mark(v); --m_num_marks; } - if (idx == 0 && !_debug_conflict) { - _debug_conflict = true; - _debug_var2position.reserve(s().num_vars()); - for (unsigned i = 0; i < lits.size(); ++i) { - _debug_var2position[lits[i].var()] = i; - } - IF_VERBOSE(0, - active2pb(m_A); - uint64_t c = 0; - for (uint64_t c1 : m_A.m_coeffs) c += c1; - verbose_stream() << "sum of coefficients: " << c << "\n"; - display(verbose_stream(), m_A, true); - verbose_stream() << "conflicting literal: " << s().m_not_l << "\n";); - - for (literal l : lits) { - if (s().is_marked(l.var())) { - IF_VERBOSE(0, verbose_stream() << "missing mark: " << l << "\n";); - s().reset_mark(l.var()); - } - } - m_num_marks = 0; - resolve_conflict(); - } --idx; } + } + + lbool ba_solver::resolve_conflict_rs() { + if (0 == m_num_propagations_since_pop) { + return l_undef; + } + m_overflow = false; + reset_coeffs(); + m_num_marks = 0; + m_bound = 0; + literal consequent = s().m_not_l; + justification js = s().m_conflict; + TRACE("ba", tout << consequent << " " << js << "\n";); + m_conflict_lvl = s().get_max_lvl(consequent, js); + if (consequent != null_literal) { + consequent.neg(); + process_antecedent(consequent, 1); + } + unsigned idx = s().m_trail.size() - 1; + + do { + // TBD: termination condition + // if UIP is below m_conflict level + + TRACE("ba", s().display_justification(tout << "process consequent: " << consequent << " : ", js) << "\n"; + active2pb(m_A); display(tout, m_A, true); + ); + switch (js.get_kind()) { + case justification::NONE: + SASSERT(consequent != null_literal); + resolve_on(consequent); + break; + case justification::BINARY: + SASSERT(consequent != null_literal); + resolve_on(consequent); + process_antecedent(js.get_literal()); + break; + case justification::TERNARY: + SASSERT(consequent != null_literal); + resolve_on(consequent); + process_antecedent(js.get_literal1()); + process_antecedent(js.get_literal2()); + break; + case justification::CLAUSE: { + clause & c = s().get_clause(js); + unsigned i = 0; + if (consequent == null_literal) { + m_bound = 1; + } + else { + resolve_on(consequent); + if (c[0] == consequent) { + i = 1; + } + else { + SASSERT(c[1] == consequent); + process_antecedent(c[0]); + i = 2; + } + } + unsigned sz = c.size(); + for (; i < sz; i++) + process_antecedent(c[i]); + break; + } + case justification::EXT_JUSTIFICATION: { + ++m_stats.m_num_resolves; + ext_justification_idx index = js.get_ext_justification_idx(); + constraint& cnstr = index2constraint(index); + constraint2pb(cnstr, consequent, 1, m_A); + if (consequent == null_literal) { + m_bound = static_cast(m_A.m_k); + for (unsigned i = m_A.size(); i-- > 0; ) { + inc_coeff(m_A.lit(i), static_cast(m_A.coeff(i))); + } + } + else { + round_to_one(consequent); + round_to_one(m_A, consequent); + resolve_with(m_A); + } + break; + } + default: + UNREACHABLE(); + break; + } + + cut(); + + // find the next marked variable in the assignment stack + bool_var v; + while (true) { + consequent = s().m_trail[idx]; + v = consequent.var(); + if (s().is_marked(v)) break; + if (idx == 0) { + goto bail_out; + } + --idx; + } + + SASSERT(lvl(v) == m_conflict_lvl); + s().reset_mark(v); + --idx; + --m_num_marks; + js = s().m_justification[v]; + } + while (m_num_marks > 0 && !m_overflow); + TRACE("ba", active2pb(m_A); display(tout, m_A, true);); + + active2constraint(); + if (!m_overflow) { + return l_true; + } + bail_out: + m_overflow = false; return l_undef; } + bool ba_solver::create_asserting_lemma() { bool adjusted = false; @@ -1461,10 +1686,17 @@ namespace sat { } if (g >= 2) { - normalize_active_coeffs(); - for (bool_var v : m_active_vars) { + reset_active_var_set(); + unsigned j = 0, sz = m_active_vars.size(); + for (unsigned i = 0; i < sz; ++i) { + bool_var v = m_active_vars[i]; + int64_t c = m_coeffs[v]; + if (m_active_var_set.contains(v) || c == 0) continue; + m_active_var_set.insert(v); m_coeffs[v] /= static_cast(g); + m_active_vars[j++] = v; } + m_active_vars.shrink(j); m_bound = (m_bound + g - 1) / g; ++m_stats.m_num_cut; } @@ -1502,7 +1734,6 @@ namespace sat { if (level > 0 && !s().is_marked(v) && level == m_conflict_lvl) { s().mark(v); - TRACE("sat_verbose", tout << "Mark: v" << v << "\n";); ++m_num_marks; if (_debug_conflict && _debug_consequent != null_literal && _debug_var2position[_debug_consequent.var()] < _debug_var2position[l.var()]) { IF_VERBOSE(0, verbose_stream() << "antecedent " << l << " is above consequent in stack\n";); @@ -1551,10 +1782,10 @@ namespace sat { if (k == 1 && lit == null_literal) { literal_vector _lits(lits); s().mk_clause(_lits.size(), _lits.c_ptr(), learned); - return 0; + return nullptr; } if (!learned && clausify(lit, lits.size(), lits.c_ptr(), k)) { - return 0; + return nullptr; } void * mem = m_allocator.allocate(card::get_obj_size(lits.size())); card* c = new (mem) card(next_id(), lit, lits, k); @@ -1615,7 +1846,7 @@ namespace sat { bool units = true; for (wliteral wl : wlits) units &= wl.first == 1; if (k == 0 && lit == null_literal) { - return 0; + return nullptr; } if (units || k == 1) { literal_vector lits; @@ -3612,7 +3843,7 @@ namespace sat { } } - void ba_solver::display(std::ostream& out, ineq& ineq, bool values) const { + void ba_solver::display(std::ostream& out, ineq const& ineq, bool values) const { for (unsigned i = 0; i < ineq.m_lits.size(); ++i) { out << ineq.m_coeffs[i] << "*" << ineq.m_lits[i] << " "; if (values) out << value(ineq.m_lits[i]) << " "; @@ -3824,37 +4055,38 @@ namespace sat { p.reset(m_bound); for (bool_var v : m_active_vars) { if (m_active_var_set.contains(v)) continue; - int64_t coeff = get_coeff(v); + unsigned coeff; + literal lit; + get_coeff(v, lit, coeff); if (coeff == 0) continue; m_active_var_set.insert(v); - literal lit(v, coeff < 0); p.m_lits.push_back(lit); - p.m_coeffs.push_back(std::abs(coeff)); + p.m_coeffs.push_back(coeff); } } - ba_solver::constraint* ba_solver::active2constraint() { + void ba_solver::active2wlits() { reset_active_var_set(); m_wlits.reset(); - uint64_t sum = 0; - if (m_bound == 1) return 0; - if (m_overflow) return 0; - + uint64_t sum = 0; for (bool_var v : m_active_vars) { - int coeff = get_int_coeff(v); + unsigned coeff; + literal lit; + get_coeff(v, lit, coeff); if (m_active_var_set.contains(v) || coeff == 0) continue; m_active_var_set.insert(v); - literal lit(v, coeff < 0); - m_wlits.push_back(wliteral(get_abs_coeff(v), lit)); - sum += get_abs_coeff(v); + m_wlits.push_back(wliteral(static_cast(coeff), lit)); + sum += coeff; } + m_overflow |= sum >= UINT_MAX/2; + } - if (m_overflow || sum >= UINT_MAX/2) { - return 0; + ba_solver::constraint* ba_solver::active2constraint() { + active2wlits(); + if (m_overflow) { + return nullptr; } - else { - return add_pb_ge(null_literal, m_wlits, m_bound, true); - } + return add_pb_ge(null_literal, m_wlits, m_bound, true); } /* @@ -3889,11 +4121,9 @@ namespace sat { ba_solver::constraint* ba_solver::active2card() { - normalize_active_coeffs(); - m_wlits.reset(); - for (bool_var v : m_active_vars) { - int coeff = get_int_coeff(v); - m_wlits.push_back(std::make_pair(get_abs_coeff(v), literal(v, coeff < 0))); + active2wlits(); + if (m_overflow) { + return nullptr; } std::sort(m_wlits.begin(), m_wlits.end(), compare_wlit()); unsigned k = 0; @@ -3905,7 +4135,7 @@ namespace sat { ++k; } if (k == 1) { - return 0; + return nullptr; } while (!m_wlits.empty()) { wliteral wl = m_wlits.back(); @@ -3928,7 +4158,9 @@ namespace sat { ++num_max_level; } } - if (m_overflow) return 0; + if (m_overflow) { + return nullptr; + } if (slack >= k) { #if 0 @@ -3963,15 +4195,18 @@ namespace sat { void ba_solver::justification2pb(justification const& js, literal lit, unsigned offset, ineq& ineq) { switch (js.get_kind()) { case justification::NONE: + SASSERT(lit != null_literal); ineq.reset(offset); ineq.push(lit, offset); break; case justification::BINARY: + SASSERT(lit != null_literal); ineq.reset(offset); ineq.push(lit, offset); ineq.push(js.get_literal(), offset); break; case justification::TERNARY: + SASSERT(lit != null_literal); ineq.reset(offset); ineq.push(lit, offset); ineq.push(js.get_literal1(), offset); @@ -3986,35 +4221,7 @@ namespace sat { case justification::EXT_JUSTIFICATION: { ext_justification_idx index = js.get_ext_justification_idx(); constraint& cnstr = index2constraint(index); - switch (cnstr.tag()) { - case card_t: { - card& c = cnstr.to_card(); - ineq.reset(offset*c.k()); - for (literal l : c) ineq.push(l, offset); - if (c.lit() != null_literal) ineq.push(~c.lit(), offset*c.k()); - break; - } - case pb_t: { - pb& p = cnstr.to_pb(); - ineq.reset(p.k()); - for (wliteral wl : p) ineq.push(wl.second, wl.first); - if (p.lit() != null_literal) ineq.push(~p.lit(), p.k()); - break; - } - case xr_t: { - xr& x = cnstr.to_xr(); - literal_vector ls; - get_antecedents(lit, x, ls); - ineq.reset(offset); - for (literal l : ls) ineq.push(~l, offset); - literal lxr = x.lit(); - if (lxr != null_literal) ineq.push(~lxr, offset); - break; - } - default: - UNREACHABLE(); - break; - } + constraint2pb(cnstr, lit, offset, ineq); break; } default: @@ -4023,6 +4230,38 @@ namespace sat { } } + void ba_solver::constraint2pb(constraint& cnstr, literal lit, unsigned offset, ineq& ineq) { + switch (cnstr.tag()) { + case card_t: { + card& c = cnstr.to_card(); + ineq.reset(offset*c.k()); + for (literal l : c) ineq.push(l, offset); + if (c.lit() != null_literal) ineq.push(~c.lit(), offset*c.k()); + break; + } + case pb_t: { + pb& p = cnstr.to_pb(); + ineq.reset(offset * p.k()); + for (wliteral wl : p) ineq.push(wl.second, offset * wl.first); + if (p.lit() != null_literal) ineq.push(~p.lit(), offset * p.k()); + break; + } + case xr_t: { + xr& x = cnstr.to_xr(); + literal_vector ls; + SASSERT(lit != null_literal); + get_antecedents(lit, x, ls); + ineq.reset(offset); + for (literal l : ls) ineq.push(~l, offset); + literal lxr = x.lit(); + if (lxr != null_literal) ineq.push(~lxr, offset); + break; + } + default: + UNREACHABLE(); + break; + } + } // validate that m_A & m_B implies m_C diff --git a/src/sat/ba_solver.h b/src/sat/ba_solver.h index bae59f45a..6adde156d 100644 --- a/src/sat/ba_solver.h +++ b/src/sat/ba_solver.h @@ -208,8 +208,14 @@ namespace sat { svector m_coeffs; uint64_t m_k; ineq(): m_k(0) {} + unsigned size() const { return m_lits.size(); } + literal lit(unsigned i) const { return m_lits[i]; } + uint64_t coeff(unsigned i) const { return m_coeffs[i]; } void reset(uint64_t k) { m_lits.reset(); m_coeffs.reset(); m_k = k; } void push(literal l, uint64_t c) { m_lits.push_back(l); m_coeffs.push_back(c); } + uint64_t coeff(literal lit) const; + void divide(uint64_t c); + void weaken(unsigned i); }; solver* m_solver; @@ -396,10 +402,22 @@ namespace sat { lbool eval(model const& m, pb const& p) const; double get_reward(pb const& p, literal_occs_fun& occs) const; + // RoundingPb conflict resolution + lbool resolve_conflict_rs(); + void round_to_one(ineq& ineq, literal lit); + void round_to_one(literal lit); + void divide(uint64_t c); + void resolve_on(literal lit); + void resolve_with(ineq const& ineq); + void reset_marks(unsigned idx); + + void bail_resolve_conflict(unsigned idx); + // access solver inline lbool value(bool_var v) const { return value(literal(v, false)); } inline lbool value(literal lit) const { return m_lookahead ? m_lookahead->value(lit) : m_solver->value(lit); } inline lbool value(model const& m, literal l) const { return l.sign() ? ~m[l.var()] : m[l.var()]; } + inline bool is_false(literal lit) const { return l_false == value(lit); } inline unsigned lvl(literal lit) const { return m_lookahead || m_unit_walk ? 0 : m_solver->lvl(lit); } inline unsigned lvl(bool_var v) const { return m_lookahead || m_unit_walk ? 0 : m_solver->lvl(v); } @@ -426,9 +444,10 @@ namespace sat { mutable bool m_overflow; void reset_active_var_set(); - void normalize_active_coeffs(); void inc_coeff(literal l, unsigned offset); int64_t get_coeff(bool_var v) const; + uint64_t get_coeff(literal lit) const; + void get_coeff(bool_var v, literal& l, unsigned& c); unsigned get_abs_coeff(bool_var v) const; int get_int_coeff(bool_var v) const; unsigned get_bound() const; @@ -436,6 +455,7 @@ namespace sat { literal get_asserting_literal(literal conseq); void process_antecedent(literal l, unsigned offset); + void process_antecedent(literal l) { process_antecedent(l, 1); } void process_card(card& c, unsigned offset); void cut(); bool create_asserting_lemma(); @@ -466,10 +486,13 @@ namespace sat { void active2pb(ineq& p); constraint* active2constraint(); constraint* active2card(); + void active2wlits(); void justification2pb(justification const& j, literal lit, unsigned offset, ineq& p); + void constraint2pb(constraint& cnstr, literal lit, unsigned offset, ineq& p); bool validate_resolvent(); + unsigned get_coeff(ineq const& pb, literal lit); - void display(std::ostream& out, ineq& p, bool values = false) const; + void display(std::ostream& out, ineq const& p, bool values = false) const; void display(std::ostream& out, card const& c, bool values) const; void display(std::ostream& out, pb const& p, bool values) const; void display(std::ostream& out, xr const& c, bool values) const; diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 48009be31..0cf2d9be4 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -701,7 +701,8 @@ namespace smt { ctx.mk_th_axiom(get_id(), 1, &lit); time_t start_lb = std::numeric_limits::max(); - time_t end_ub = 0; + time_t runtime_lb = std::numeric_limits::max(); + time_t end_ub = 0, runtime_ub = 0; for (job_resource const& jr : ji.m_resources) { unsigned r = jr.m_resource_id; res_info const& ri = m_resources[r]; @@ -714,6 +715,9 @@ namespace smt { if (last_available(jr, ri, idx)) { end_ub = std::max(end_ub, ri.m_available[idx].m_end); } + runtime_lb = std::min(runtime_lb, jr.m_capacity); + // TBD: more accurate estimates for runtime_lb based on gaps + // TBD: correct estimate of runtime_ub taking gaps into account. } // start(j) >= start_lb @@ -723,6 +727,9 @@ namespace smt { // end(j) <= end_ub lit = mk_le(ji.m_end, end_ub); ctx.mk_th_axiom(get_id(), 1, &lit); + + // start(j) + runtime_lb <= end(j) + // end(j) <= start(j) + runtime_ub } TRACE("csp", tout << "add-done end\n";); From c39d7c8565c1cbf37252c40ace6900c79c834f8b Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 3 Sep 2018 11:17:50 -0700 Subject: [PATCH 30/65] updated resolvents Signed-off-by: Nikolaj Bjorner --- src/opt/opt_context.cpp | 2 +- src/sat/ba_solver.cpp | 443 ++++++++++++++++++++++++++-------------- src/sat/ba_solver.h | 46 +++-- src/sat/sat_big.cpp | 34 +-- src/sat/sat_big.h | 6 +- src/sat/sat_scc.h | 1 - 6 files changed, 349 insertions(+), 183 deletions(-) diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index e5c0bcddb..5d4eb3fc5 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -351,7 +351,7 @@ namespace opt { void context::get_model_core(model_ref& mdl) { mdl = m_model; fix_model(mdl); - mdl->set_model_completion(true); + if (mdl) mdl->set_model_completion(true); TRACE("opt", tout << *mdl;); } diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index c86ea3317..2c33bd955 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -49,6 +49,11 @@ namespace sat { return static_cast(*this); } + ba_solver::pb_base const& ba_solver::constraint::to_pb_base() const{ + SASSERT(is_pb() || is_card()); + return static_cast(*this); + } + ba_solver::xr& ba_solver::constraint::to_xr() { SASSERT(is_xr()); return static_cast(*this); @@ -1015,6 +1020,7 @@ namespace sat { bool_var v = l.var(); SASSERT(v != null_bool_var); m_coeffs.reserve(v + 1, 0); + TRACE("ba_verbose", tout << l << " " << offset << "\n";); int64_t coeff0 = m_coeffs[v]; if (coeff0 == 0) { @@ -1064,6 +1070,7 @@ namespace sat { l = literal(v, c1 < 0); c1 = std::abs(c1); c = static_cast(c1); + // TRACE("ba", tout << l << " " << c << "\n";); m_overflow |= c != c1; } @@ -1096,6 +1103,17 @@ namespace sat { m_active_vars.reset(); } + void ba_solver::init_visited() { + m_visited_ts++; + if (m_visited_ts == 0) { + m_visited_ts = 1; + m_visited.reset(); + } + while (m_visited.size() < 2*s().num_vars()) { + m_visited.push_back(0); + } + } + static bool _debug_conflict = false; static literal _debug_consequent = null_literal; static unsigned_vector _debug_var2position; @@ -1120,7 +1138,7 @@ namespace sat { IF_VERBOSE(0, active2pb(m_A); uint64_t c = 0; - for (uint64_t c1 : m_A.m_coeffs) c += c1; + for (wliteral l : m_A.m_wlits) c += l.first; verbose_stream() << "sum of coefficients: " << c << "\n"; display(verbose_stream(), m_A, true); verbose_stream() << "conflicting literal: " << s().m_not_l << "\n";); @@ -1154,6 +1172,9 @@ namespace sat { justification js = s().m_conflict; TRACE("ba", tout << consequent << " " << js << "\n";); m_conflict_lvl = s().get_max_lvl(consequent, js); + if (m_conflict_lvl == 0) { + return l_undef; + } if (consequent != null_literal) { consequent.neg(); process_antecedent(consequent, 1); @@ -1316,27 +1337,14 @@ namespace sat { DEBUG_CODE(for (bool_var i = 0; i < static_cast(s().num_vars()); ++i) SASSERT(!s().is_marked(i));); SASSERT(validate_lemma()); - if (!create_asserting_lemma()) { goto bail_out; } + active2card(); + DEBUG_CODE(VERIFY(validate_conflict(m_lemma, m_A));); - TRACE("ba", tout << m_lemma << "\n";); - - if (get_config().m_drat) { - svector ps; // TBD fill in - drat_add(m_lemma, ps); - } - - s().m_lemma.reset(); - s().m_lemma.append(m_lemma); - for (unsigned i = 1; i < m_lemma.size(); ++i) { - CTRACE("ba", s().is_marked(m_lemma[i].var()), tout << "marked: " << m_lemma[i] << "\n";); - s().mark(m_lemma[i].var()); - } - return l_true; bail_out: @@ -1345,8 +1353,7 @@ namespace sat { } - uint64_t ba_solver::ineq::coeff(literal l) const { - bool_var v = l.var(); + unsigned ba_solver::ineq::bv_coeff(bool_var v) const { for (unsigned i = size(); i-- > 0; ) { if (lit(i).var() == v) return coeff(i); } @@ -1354,10 +1361,10 @@ namespace sat { return 0; } - void ba_solver::ineq::divide(uint64_t c) { + void ba_solver::ineq::divide(unsigned c) { if (c == 1) return; for (unsigned i = size(); i-- > 0; ) { - m_coeffs[i] = (m_coeffs[i] + c - 1) / c; + m_wlits[i].first = (coeff(i) + c - 1) / c; } m_k = (m_k + c - 1) / c; } @@ -1366,48 +1373,57 @@ namespace sat { * Remove literal at position i, subtract coefficient from bound. */ void ba_solver::ineq::weaken(unsigned i) { - uint64_t ci = coeff(i); + unsigned ci = coeff(i); SASSERT(m_k >= ci); m_k -= ci; - m_lits[i] = m_lits.back(); - m_coeffs[i] = m_coeffs.back(); - m_lits.pop_back(); - m_coeffs.pop_back(); + m_wlits[i] = m_wlits.back(); + m_wlits.pop_back(); } /** * Round coefficient of inequality to 1. */ - void ba_solver::round_to_one(ineq& ineq, literal lit) { - uint64_t c = ineq.coeff(lit); + void ba_solver::round_to_one(ineq& ineq, bool_var v) { + unsigned c = ineq.bv_coeff(v); if (c == 1) return; unsigned sz = ineq.size(); for (unsigned i = 0; i < sz; ++i) { - uint64_t ci = ineq.coeff(i); - if (ci % c != 0 && !is_false(ineq.lit(i))) { - ineq.weaken(i); - --i; - --sz; + unsigned ci = ineq.coeff(i); + unsigned q = ci % c; + if (q != 0 && !is_false(ineq.lit(i))) { + if (q == ci) { + ineq.weaken(i); + --i; + --sz; + } + else { + ineq.m_wlits[i].first -= q; + ineq.m_k -= q; + } } } ineq.divide(c); } - void ba_solver::round_to_one(literal lit) { - uint64_t c = get_coeff(lit); - if (c == 1) return; + void ba_solver::round_to_one(bool_var w) { + unsigned c = get_abs_coeff(w); + if (c == 1 || c == 0) return; for (bool_var v : m_active_vars) { literal l; unsigned ci; get_coeff(v, l, ci); - if (ci > 0 && ci % c != 0 && !is_false(l)) { - m_coeffs[v] = 0; + unsigned q = ci % c; + if (q != 0 && !is_false(l)) { + m_coeffs[v] = ci - q; + m_bound -= q; + SASSERT(m_bound > 0); } } divide(c); + SASSERT(validate_lemma()); } - void ba_solver::divide(uint64_t c) { + void ba_solver::divide(unsigned c) { SASSERT(c != 0); if (c == 1) return; reset_active_var_set(); @@ -1426,20 +1442,17 @@ namespace sat { m_active_vars[j++] = v; } m_active_vars.shrink(j); - if (m_bound % c != 0) { - ++m_stats.m_num_cut; - m_bound = static_cast((m_bound + c - 1) / c); - } + m_bound = static_cast((m_bound + c - 1) / c); } void ba_solver::resolve_on(literal consequent) { - round_to_one(consequent); + round_to_one(consequent.var()); m_coeffs[consequent.var()] = 0; } void ba_solver::resolve_with(ineq const& ineq) { TRACE("ba", display(tout, ineq, true);); - inc_bound(1 + ineq.m_k); + inc_bound(ineq.m_k); for (unsigned i = ineq.size(); i-- > 0; ) { literal l = ineq.lit(i); inc_coeff(l, static_cast(ineq.coeff(i))); @@ -1457,6 +1470,23 @@ namespace sat { --idx; } } + + /** + * \brief mark variables that are on the assignment stack but + * below the current processing level. + */ + void ba_solver::mark_variables(ineq const& ineq) { + for (wliteral wl : ineq.m_wlits) { + literal l = wl.second; + if (!is_false(l)) continue; + bool_var v = l.var(); + unsigned level = lvl(v); + if (!s().is_marked(v) && !is_visited(v) && level == m_conflict_lvl) { + s().mark(v); + ++m_num_marks; + } + } + } lbool ba_solver::resolve_conflict_rs() { if (0 == m_num_propagations_since_pop) { @@ -1464,49 +1494,56 @@ namespace sat { } m_overflow = false; reset_coeffs(); + init_visited(); m_num_marks = 0; m_bound = 0; literal consequent = s().m_not_l; justification js = s().m_conflict; - TRACE("ba", tout << consequent << " " << js << "\n";); m_conflict_lvl = s().get_max_lvl(consequent, js); + if (m_conflict_lvl == 0) { + return l_undef; + } if (consequent != null_literal) { consequent.neg(); process_antecedent(consequent, 1); } + TRACE("ba", tout << consequent << " " << js << "\n";); unsigned idx = s().m_trail.size() - 1; do { - // TBD: termination condition - // if UIP is below m_conflict level - TRACE("ba", s().display_justification(tout << "process consequent: " << consequent << " : ", js) << "\n"; - active2pb(m_A); display(tout, m_A, true); + if (consequent != null_literal) { active2pb(m_A); display(tout, m_A, true); } ); + switch (js.get_kind()) { case justification::NONE: SASSERT(consequent != null_literal); - resolve_on(consequent); + inc_bound(1); + round_to_one(consequent.var()); + inc_coeff(consequent, 1); break; case justification::BINARY: SASSERT(consequent != null_literal); - resolve_on(consequent); + inc_bound(1); + round_to_one(consequent.var()); + inc_coeff(consequent, 1); process_antecedent(js.get_literal()); break; case justification::TERNARY: SASSERT(consequent != null_literal); - resolve_on(consequent); + inc_bound(1); + round_to_one(consequent.var()); + inc_coeff(consequent, 1); process_antecedent(js.get_literal1()); process_antecedent(js.get_literal2()); break; case justification::CLAUSE: { + inc_bound(1); clause & c = s().get_clause(js); unsigned i = 0; - if (consequent == null_literal) { - m_bound = 1; - } - else { - resolve_on(consequent); + if (consequent != null_literal) { + round_to_one(consequent.var()); + inc_coeff(consequent, 1); if (c[0] == consequent) { i = 1; } @@ -1525,25 +1562,51 @@ namespace sat { ++m_stats.m_num_resolves; ext_justification_idx index = js.get_ext_justification_idx(); constraint& cnstr = index2constraint(index); - constraint2pb(cnstr, consequent, 1, m_A); + switch (cnstr.tag()) { + case card_t: + case pb_t: { + pb_base const& p = cnstr.to_pb_base(); + unsigned k = p.k(), sz = p.size(); + m_A.reset(0); + for (unsigned i = 0; i < sz; ++i) { + literal l = p.get_lit(i); + unsigned c = p.get_coeff(i); + if (l == consequent || !is_visited(l.var())) { + m_A.push(l, c); + } + else { + SASSERT(k > c); + k -= c; + } + } + SASSERT(k > 0); + if (p.lit() != null_literal) m_A.push(~p.lit(), k); + m_A.m_k = k; + break; + } + default: + constraint2pb(cnstr, consequent, 1, m_A); + break; + } + mark_variables(m_A); if (consequent == null_literal) { m_bound = static_cast(m_A.m_k); - for (unsigned i = m_A.size(); i-- > 0; ) { - inc_coeff(m_A.lit(i), static_cast(m_A.coeff(i))); + for (wliteral wl : m_A.m_wlits) { + process_antecedent(wl.second, wl.first); } } else { - round_to_one(consequent); - round_to_one(m_A, consequent); + round_to_one(consequent.var()); + if (cnstr.tag() == pb_t) round_to_one(m_A, consequent.var()); resolve_with(m_A); } - break; } default: UNREACHABLE(); break; } + SASSERT(validate_lemma()); cut(); // find the next marked variable in the assignment stack @@ -1551,7 +1614,14 @@ namespace sat { while (true) { consequent = s().m_trail[idx]; v = consequent.var(); - if (s().is_marked(v)) break; + mark_visited(v); + if (s().is_marked(v)) { + if (get_coeff(v) != 0) { + break; + } + s().reset_mark(v); + --m_num_marks; + } if (idx == 0) { goto bail_out; } @@ -1567,20 +1637,24 @@ namespace sat { while (m_num_marks > 0 && !m_overflow); TRACE("ba", active2pb(m_A); display(tout, m_A, true);); - active2constraint(); - if (!m_overflow) { +#if 0 + // why this? + if (!m_overflow && consequent != null_literal) { + round_to_one(consequent.var()); + } +#endif + if (!m_overflow && create_asserting_lemma()) { + active2constraint(); return l_true; } bail_out: + IF_VERBOSE(1, verbose_stream() << "bail\n"); m_overflow = false; return l_undef; } bool ba_solver::create_asserting_lemma() { - bool adjusted = false; - - adjust_conflict_level: int64_t bound64 = m_bound; int64_t slack = -bound64; for (bool_var v : m_active_vars) { @@ -1629,20 +1703,22 @@ namespace sat { if (m_lemma[0] == null_literal) { if (m_lemma.size() == 1) { s().set_conflict(justification()); - return false; } return false; - unsigned old_level = m_conflict_lvl; - m_conflict_lvl = 0; - for (unsigned i = 1; i < m_lemma.size(); ++i) { - m_conflict_lvl = std::max(m_conflict_lvl, lvl(m_lemma[i])); - } - IF_VERBOSE(1, verbose_stream() << "(sat.backjump :new-level " << m_conflict_lvl << " :old-level " << old_level << ")\n";); - adjusted = true; - goto adjust_conflict_level; } - if (!adjusted) { - active2card(); + + TRACE("ba", tout << m_lemma << "\n";); + + if (get_config().m_drat) { + svector ps; // TBD fill in + drat_add(m_lemma, ps); + } + + s().m_lemma.reset(); + s().m_lemma.append(m_lemma); + for (unsigned i = 1; i < m_lemma.size(); ++i) { + CTRACE("ba", s().is_marked(m_lemma[i].var()), tout << "marked: " << m_lemma[i] << "\n";); + s().mark(m_lemma[i].var()); } return true; } @@ -1732,7 +1808,7 @@ namespace sat { bool_var v = l.var(); unsigned level = lvl(v); - if (level > 0 && !s().is_marked(v) && level == m_conflict_lvl) { + if (!s().is_marked(v) && level == m_conflict_lvl) { s().mark(v); ++m_num_marks; if (_debug_conflict && _debug_consequent != null_literal && _debug_var2position[_debug_consequent.var()] < _debug_var2position[l.var()]) { @@ -2724,7 +2800,8 @@ namespace sat { set_non_external(); if (get_config().m_elim_vars) elim_pure(); for (unsigned sz = m_constraints.size(), i = 0; i < sz; ++i) subsumption(*m_constraints[i]); - for (unsigned sz = m_learned.size(), i = 0; i < sz; ++i) subsumption(*m_learned[i]); + for (unsigned sz = m_learned.size(), i = 0; i < sz; ++i) subsumption(*m_learned[i]); + unit_strengthen(); cleanup_clauses(); cleanup_constraints(); update_pure(); @@ -3170,9 +3247,10 @@ namespace sat { bool found_dup = false; bool found_root = false; + init_visited(); for (unsigned i = 0; i < c.size(); ++i) { literal l = c.get_lit(i); - if (is_marked(l)) { + if (is_visited(l)) { found_dup = true; break; } @@ -3182,10 +3260,7 @@ namespace sat { } } for (unsigned i = 0; i < c.size(); ++i) { - literal l = c.get_lit(i); - unmark_visited(l); - unmark_visited(~l); - found_root |= l.var() == root.var(); + found_root |= c.get_lit(i).var() == root.var(); } if (found_root) { @@ -3342,6 +3417,78 @@ namespace sat { return pure_literals; } + /** + * Strengthen inequalities using binary implication information. + * + * x -> ~y, x -> ~z, y + z + u >= 2 + * ---------------------------------- + * y + z + u + ~x >= 3 + * + * for c : constraints + * for l : c: + * slack <- of c under root(~l) + * if slack < 0: + * add ~root(~l) to c, k <- k + 1 + */ + void ba_solver::unit_strengthen() { + big big(s().m_rand); + big.init(s(), true); + for (unsigned sz = m_constraints.size(), i = 0; i < sz; ++i) + unit_strengthen(big, *m_constraints[i]); + for (unsigned sz = m_learned.size(), i = 0; i < sz; ++i) + unit_strengthen(big, *m_learned[i]); + } + + void ba_solver::unit_strengthen(big& big, constraint& c) { + if (c.was_removed()) return; + switch (c.tag()) { + case card_t: + unit_strengthen(big, c.to_card()); + break; + case pb_t: + unit_strengthen(big, c.to_pb()); + break; + default: + break; + } + } + + void ba_solver::unit_strengthen(big& big, card& c) { + for (literal l : c) { + literal r = big.get_root(~l); + if (r == ~l) continue; + unsigned k = c.k(); + for (literal u : c) { + if (big.reaches(r, ~u)) { + if (k == 0) { + // ~r + C >= c.k() + 1 + IF_VERBOSE(0, verbose_stream() << "TBD add " << ~r << " to " << c << "\n";); + return; + } + --k; + } + } + } + } + + void ba_solver::unit_strengthen(big& big, pb& p) { + for (wliteral wl : p) { + literal r = big.get_root(~wl.second); + if (r == ~wl.second) continue; + unsigned k = p.k(); + for (wliteral u : p) { + if (big.reaches(r, ~u.second)) { + if (k < u.first) { + // ~r + p >= p.k() + 1 + IF_VERBOSE(0, verbose_stream() << "TBD add " << ~r << " to " << p << "\n";); + return; + } + k -= u.first; + } + } + } + } + void ba_solver::subsumption(constraint& cnstr) { if (cnstr.was_removed()) return; switch (cnstr.tag()) { @@ -3430,10 +3577,10 @@ namespace sat { unsigned common = 0; comp.reset(); for (literal l : c2) { - if (is_marked(l)) { + if (is_visited(l)) { ++common; } - else if (is_marked(~l)) { + else if (is_visited(~l)) { comp.push_back(l); } else { @@ -3455,10 +3602,10 @@ namespace sat { self = false; for (literal l : c2) { - if (is_marked(l)) { + if (is_visited(l)) { ++common; } - else if (is_marked(~l)) { + else if (is_visited(~l)) { ++complement; } else { @@ -3482,10 +3629,10 @@ namespace sat { unsigned num_sub = 0; for (unsigned i = 0; i < p2.size(); ++i) { literal l = p2.get_lit(i); - if (is_marked(l) && m_weights[l.index()] <= p2.get_coeff(i)) { + if (is_visited(l) && m_weights[l.index()] <= p2.get_coeff(i)) { ++num_sub; } - if (p1.size() + i > p2.size() + num_sub) return false; + if (p1.size() + i > p2.size() + num_sub) return false; } return num_sub == p1.size(); } @@ -3550,7 +3697,7 @@ namespace sat { clear_watch(c2); unsigned j = 0; for (unsigned i = 0; i < c2.size(); ++i) { - if (!is_marked(~c2[i])) { + if (!is_visited(~c2[i])) { c2[j++] = c2[i]; } } @@ -3586,7 +3733,7 @@ namespace sat { void ba_solver::binary_subsumption(card& c1, literal lit) { if (c1.k() + 1 != c1.size()) return; - SASSERT(is_marked(lit)); + SASSERT(is_visited(lit)); SASSERT(!c1.was_removed()); watch_list & wlist = get_wlist(~lit); watch_list::iterator it = wlist.begin(); @@ -3594,7 +3741,7 @@ namespace sat { watch_list::iterator end = wlist.end(); for (; it != end; ++it) { watched w = *it; - if (w.is_binary_clause() && is_marked(w.get_literal())) { + if (w.is_binary_clause() && is_visited(w.get_literal())) { ++m_stats.m_num_bin_subsumes; IF_VERBOSE(10, verbose_stream() << c1 << " subsumes (" << lit << " " << w.get_literal() << ")\n";); if (!w.is_learned()) { @@ -3616,6 +3763,7 @@ namespace sat { return; } clause_vector removed_clauses; + init_visited(); for (literal l : c1) mark_visited(l); for (unsigned i = 0; i < std::min(c1.size(), c1.k() + 1); ++i) { literal lit = c1[i]; @@ -3623,7 +3771,6 @@ namespace sat { clause_subsumption(c1, lit, removed_clauses); binary_subsumption(c1, lit); } - for (literal l : c1) unmark_visited(l); m_clause_removed |= !removed_clauses.empty(); for (clause *c : removed_clauses) { c->set_removed(true); @@ -3635,6 +3782,7 @@ namespace sat { if (p1.was_removed() || p1.lit() != null_literal) { return; } + init_visited(); for (wliteral l : p1) { SASSERT(m_weights[l.second.index()] == 0); m_weights.setx(l.second.index(), l.first, 0); @@ -3646,7 +3794,6 @@ namespace sat { } for (wliteral l : p1) { m_weights[l.second.index()] = 0; - unmark_visited(l.second); } } @@ -3844,9 +3991,9 @@ namespace sat { } void ba_solver::display(std::ostream& out, ineq const& ineq, bool values) const { - for (unsigned i = 0; i < ineq.m_lits.size(); ++i) { - out << ineq.m_coeffs[i] << "*" << ineq.m_lits[i] << " "; - if (values) out << value(ineq.m_lits[i]) << " "; + for (unsigned i = 0; i < ineq.size(); ++i) { + out << ineq.coeff(i) << "*" << ineq.lit(i) << " "; + if (values) out << value(ineq.lit(i)) << " "; } out << ">= " << ineq.m_k << "\n"; } @@ -4031,14 +4178,11 @@ namespace sat { reset_active_var_set(); for (bool_var v : m_active_vars) { if (m_active_var_set.contains(v)) continue; - int64_t coeff = get_coeff(v); + unsigned coeff; + literal lit; + get_coeff(v, lit, coeff); if (coeff == 0) continue; - m_active_var_set.insert(v); - literal lit(v, false); - if (coeff < 0 && value(lit) != l_true) { - val -= coeff; - } - else if (coeff > 0 && value(lit) != l_false) { + if (!is_false(lit)) { val += coeff; } } @@ -4051,31 +4195,26 @@ namespace sat { } void ba_solver::active2pb(ineq& p) { - reset_active_var_set(); p.reset(m_bound); + active2wlits(p.m_wlits); + } + + void ba_solver::active2wlits() { + m_wlits.reset(); + active2wlits(m_wlits); + } + + void ba_solver::active2wlits(svector& wlits) { + reset_active_var_set(); + uint64_t sum = 0; for (bool_var v : m_active_vars) { - if (m_active_var_set.contains(v)) continue; + if (m_active_var_set.contains(v)) continue; unsigned coeff; literal lit; get_coeff(v, lit, coeff); if (coeff == 0) continue; m_active_var_set.insert(v); - p.m_lits.push_back(lit); - p.m_coeffs.push_back(coeff); - } - } - - void ba_solver::active2wlits() { - reset_active_var_set(); - m_wlits.reset(); - uint64_t sum = 0; - for (bool_var v : m_active_vars) { - unsigned coeff; - literal lit; - get_coeff(v, lit, coeff); - if (m_active_var_set.contains(v) || coeff == 0) continue; - m_active_var_set.insert(v); - m_wlits.push_back(wliteral(static_cast(coeff), lit)); + wlits.push_back(wliteral(coeff, lit)); sum += coeff; } m_overflow |= sum >= UINT_MAX/2; @@ -4086,7 +4225,9 @@ namespace sat { if (m_overflow) { return nullptr; } - return add_pb_ge(null_literal, m_wlits, m_bound, true); + constraint* c = add_pb_ge(null_literal, m_wlits, m_bound, true); + TRACE("ba", if (c) display(tout, *c, true);); + return c; } /* @@ -4269,14 +4410,14 @@ namespace sat { return true; u_map coeffs; uint64_t k = m_A.m_k + m_B.m_k; - for (unsigned i = 0; i < m_A.m_lits.size(); ++i) { - uint64_t coeff = m_A.m_coeffs[i]; - SASSERT(!coeffs.contains(m_A.m_lits[i].index())); - coeffs.insert(m_A.m_lits[i].index(), coeff); + for (unsigned i = 0; i < m_A.size(); ++i) { + uint64_t coeff = m_A.coeff(i); + SASSERT(!coeffs.contains(m_A.lit(i).index())); + coeffs.insert(m_A.lit(i).index(), coeff); } - for (unsigned i = 0; i < m_B.m_lits.size(); ++i) { - uint64_t coeff1 = m_B.m_coeffs[i], coeff2; - literal lit = m_B.m_lits[i]; + for (unsigned i = 0; i < m_B.size(); ++i) { + uint64_t coeff1 = m_B.coeff(i), coeff2; + literal lit = m_B.lit(i); if (coeffs.find((~lit).index(), coeff2)) { if (coeff1 == coeff2) { coeffs.remove((~lit).index()); @@ -4301,11 +4442,11 @@ namespace sat { } } // C is above the sum of A and B - for (unsigned i = 0; i < m_C.m_lits.size(); ++i) { - literal lit = m_C.m_lits[i]; + for (unsigned i = 0; i < m_C.size(); ++i) { + literal lit = m_C.lit(i); uint64_t coeff; if (coeffs.find(lit.index(), coeff)) { - if (coeff > m_C.m_coeffs[i] && m_C.m_coeffs[i] < m_C.m_k) { + if (coeff > m_C.coeff(i) && m_C.coeff(i) < m_C.m_k) { goto violated; } coeffs.remove(lit.index()); @@ -4352,15 +4493,15 @@ namespace sat { */ literal ba_solver::translate_to_sat(solver& s, u_map& translation, ineq const& pb) { SASSERT(pb.m_k > 0); - if (pb.m_lits.size() > 1) { + if (pb.size() > 1) { ineq a, b; a.reset(pb.m_k); b.reset(pb.m_k); - for (unsigned i = 0; i < pb.m_lits.size()/2; ++i) { - a.push(pb.m_lits[i], pb.m_coeffs[i]); + for (unsigned i = 0; i < pb.size()/2; ++i) { + a.push(pb.lit(i), pb.coeff(i)); } - for (unsigned i = pb.m_lits.size()/2; i < pb.m_lits.size(); ++i) { - b.push(pb.m_lits[i], pb.m_coeffs[i]); + for (unsigned i = pb.size()/2; i < pb.size(); ++i) { + b.push(pb.lit(i), pb.coeff(i)); } bool_var v = s.mk_var(); literal lit(v, false); @@ -4372,8 +4513,8 @@ namespace sat { s.mk_clause(lits); return lit; } - if (pb.m_coeffs[0] >= pb.m_k) { - return translate_to_sat(s, translation, pb.m_lits[0]); + if (pb.coeff(0) >= pb.m_k) { + return translate_to_sat(s, translation, pb.lit(0)); } else { return null_literal; @@ -4425,9 +4566,9 @@ namespace sat { ba_solver::ineq ba_solver::negate(ineq const& a) const { ineq result; uint64_t sum = 0; - for (unsigned i = 0; i < a.m_lits.size(); ++i) { - result.push(~a.m_lits[i], a.m_coeffs[i]); - sum += a.m_coeffs[i]; + for (unsigned i = 0; i < a.size(); ++i) { + result.push(~a.lit(i), a.coeff(i)); + sum += a.coeff(i); } SASSERT(sum >= a.m_k + 1); result.m_k = sum + 1 - a.m_k; @@ -4446,15 +4587,15 @@ namespace sat { TRACE("ba", tout << "literal " << l << " is not false\n";); return false; } - if (!p.m_lits.contains(l)) { + if (!p.contains(l)) { TRACE("ba", tout << "lemma contains literal " << l << " not in inequality\n";); return false; } } uint64_t value = 0; - for (unsigned i = 0; i < p.m_lits.size(); ++i) { - uint64_t coeff = p.m_coeffs[i]; - if (!lits.contains(p.m_lits[i])) { + for (unsigned i = 0; i < p.size(); ++i) { + uint64_t coeff = p.coeff(i); + if (!lits.contains(p.lit(i))) { value += coeff; } } diff --git a/src/sat/ba_solver.h b/src/sat/ba_solver.h index 6adde156d..9418cdd99 100644 --- a/src/sat/ba_solver.h +++ b/src/sat/ba_solver.h @@ -25,6 +25,7 @@ Revision History: #include "sat/sat_solver.h" #include "sat/sat_lookahead.h" #include "sat/sat_unit_walk.h" +#include "sat/sat_big.h" #include "util/scoped_ptr_vector.h" #include "util/sorting_network.h" @@ -57,6 +58,7 @@ namespace sat { class card; class pb; class xr; + class pb_base; class constraint { protected: @@ -104,6 +106,7 @@ namespace sat { card const& to_card() const; pb const& to_pb() const; xr const& to_xr() const; + pb_base const& to_pb_base() const; bool is_card() const { return m_tag == card_t; } bool is_pb() const { return m_tag == pb_t; } bool is_xr() const { return m_tag == xr_t; } @@ -118,7 +121,7 @@ namespace sat { }; friend std::ostream& operator<<(std::ostream& out, constraint const& c); - + // base class for pb and cardinality constraints class pb_base : public constraint { protected: @@ -204,18 +207,18 @@ namespace sat { protected: struct ineq { - literal_vector m_lits; - svector m_coeffs; + svector m_wlits; uint64_t m_k; ineq(): m_k(0) {} - unsigned size() const { return m_lits.size(); } - literal lit(unsigned i) const { return m_lits[i]; } - uint64_t coeff(unsigned i) const { return m_coeffs[i]; } - void reset(uint64_t k) { m_lits.reset(); m_coeffs.reset(); m_k = k; } - void push(literal l, uint64_t c) { m_lits.push_back(l); m_coeffs.push_back(c); } - uint64_t coeff(literal lit) const; - void divide(uint64_t c); + unsigned size() const { return m_wlits.size(); } + literal lit(unsigned i) const { return m_wlits[i].second; } + unsigned coeff(unsigned i) const { return m_wlits[i].first; } + void reset(uint64_t k) { m_wlits.reset(); m_k = k; } + void push(literal l, unsigned c) { m_wlits.push_back(wliteral(c,l)); } + unsigned bv_coeff(bool_var v) const; + void divide(unsigned c); void weaken(unsigned i); + bool contains(literal l) const { for (auto wl : m_wlits) if (wl.second == l) return true; return false; } }; solver* m_solver; @@ -279,7 +282,8 @@ namespace sat { // simplification routines - svector m_visited; + svector m_visited; + unsigned m_visited_ts; vector> m_cnstr_use_list; use_list m_clause_use_list; bool m_simplify_change; @@ -298,9 +302,11 @@ namespace sat { void binary_subsumption(card& c1, literal lit); void clause_subsumption(card& c1, literal lit, clause_vector& removed_clauses); void card_subsumption(card& c1, literal lit); - void mark_visited(literal l) { m_visited[l.index()] = true; } - void unmark_visited(literal l) { m_visited[l.index()] = false; } - bool is_marked(literal l) const { return m_visited[l.index()] != 0; } + void init_visited(); + void mark_visited(literal l) { m_visited[l.index()] = m_visited_ts; } + void mark_visited(bool_var v) { mark_visited(literal(v, false)); } + bool is_visited(bool_var v) const { return is_visited(literal(v, false)); } + bool is_visited(literal l) const { return m_visited[l.index()] == m_visited_ts; } unsigned get_num_unblocked_bin(literal l); literal get_min_occurrence_literal(card const& c); void init_use_lists(); @@ -308,6 +314,10 @@ namespace sat { unsigned set_non_external(); unsigned elim_pure(); bool elim_pure(literal lit); + void unit_strengthen(); + void unit_strengthen(big& big, constraint& cs); + void unit_strengthen(big& big, card& c); + void unit_strengthen(big& big, pb& p); void subsumption(constraint& c1); void subsumption(card& c1); void gc_half(char const* _method); @@ -404,12 +414,13 @@ namespace sat { // RoundingPb conflict resolution lbool resolve_conflict_rs(); - void round_to_one(ineq& ineq, literal lit); - void round_to_one(literal lit); - void divide(uint64_t c); + void round_to_one(ineq& ineq, bool_var v); + void round_to_one(bool_var v); + void divide(unsigned c); void resolve_on(literal lit); void resolve_with(ineq const& ineq); void reset_marks(unsigned idx); + void mark_variables(ineq const& ineq); void bail_resolve_conflict(unsigned idx); @@ -487,6 +498,7 @@ namespace sat { constraint* active2constraint(); constraint* active2card(); void active2wlits(); + void active2wlits(svector& wlits); void justification2pb(justification const& j, literal lit, unsigned offset, ineq& p); void constraint2pb(constraint& cnstr, literal lit, unsigned offset, ineq& p); bool validate_resolvent(); diff --git a/src/sat/sat_big.cpp b/src/sat/sat_big.cpp index 35898a110..c1eeecd27 100644 --- a/src/sat/sat_big.cpp +++ b/src/sat/sat_big.cpp @@ -22,7 +22,8 @@ Revision History: namespace sat { big::big(random_gen& rand): - m_rand(rand) { + m_rand(rand), + m_include_cardinality(false) { } void big::init(solver& s, bool learned) { @@ -42,22 +43,22 @@ namespace sat { m_roots[v.index()] = false; edges.push_back(v); } -#if 0 - if (w.is_ext_constraint() && + if (m_include_cardinality && + w.is_ext_constraint() && s.m_ext && - learned && + learned && // cannot (yet) observe if ext constraints are learned !seen_idx.contains(w.get_ext_constraint_idx()) && s.m_ext->is_extended_binary(w.get_ext_constraint_idx(), r)) { seen_idx.insert(w.get_ext_constraint_idx(), true); - for (unsigned i = 0; i < r.size(); ++i) { - literal u = r[i]; - for (unsigned j = i + 1; j < r.size(); ++j) { - // add ~r[i] -> r[j] - literal v = r[j]; - literal u = ~r[j]; + for (unsigned i = 0; i < std::min(4u, r.size()); ++i) { + shuffle(r.size(), r.c_ptr(), m_rand); + literal u = r[0]; + for (unsigned j = 1; j < r.size(); ++j) { + literal v = ~r[j]; + // add u -> v m_roots[v.index()] = false; m_dag[u.index()].push_back(v); - // add ~r[j] -> r[i] + // add ~v -> ~u v.neg(); u.neg(); m_roots[u.index()] = false; @@ -65,7 +66,6 @@ namespace sat { } } } -#endif } } done_adding_edges(); @@ -268,6 +268,16 @@ namespace sat { return out << v; } + literal big::get_root(literal l) { + literal r = l; + do { + l = r; + r = m_root[l.index()]; + } + while (r != l); + return r; + } + void big::display(std::ostream& out) const { unsigned idx = 0; for (auto& next : m_dag) { diff --git a/src/sat/sat_big.h b/src/sat/sat_big.h index 898ddd1e8..25093fd60 100644 --- a/src/sat/sat_big.h +++ b/src/sat/sat_big.h @@ -34,6 +34,7 @@ namespace sat { svector m_left, m_right; literal_vector m_root, m_parent; bool m_learned; + bool m_include_cardinality; svector> m_del_bin; @@ -54,6 +55,9 @@ namespace sat { // static svector> s_del_bin; big(random_gen& rand); + + void set_include_cardinality(bool f) { m_include_cardinality = f; } + /** \brief initialize a BIG from a solver. */ @@ -77,7 +81,7 @@ namespace sat { int get_left(literal l) const { return m_left[l.index()]; } int get_right(literal l) const { return m_right[l.index()]; } literal get_parent(literal l) const { return m_parent[l.index()]; } - literal get_root(literal l) const { return m_root[l.index()]; } + literal get_root(literal l); bool reaches(literal u, literal v) const { return m_left[u.index()] < m_left[v.index()] && m_right[v.index()] < m_right[u.index()]; } bool connected(literal u, literal v) const { return reaches(u, v) || reaches(~v, ~u); } void display(std::ostream& out) const; diff --git a/src/sat/sat_scc.h b/src/sat/sat_scc.h index 146bd2366..1ba646992 100644 --- a/src/sat/sat_scc.h +++ b/src/sat/sat_scc.h @@ -60,7 +60,6 @@ namespace sat { void ensure_big(bool learned) { m_big.ensure_big(m_solver, learned); } int get_left(literal l) const { return m_big.get_left(l); } int get_right(literal l) const { return m_big.get_right(l); } - literal get_root(literal l) const { return m_big.get_root(l); } bool connected(literal u, literal v) const { return m_big.connected(u, v); } }; }; From c8730daea783dd8bbb9ae46230d5e0b3235ddfb7 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 3 Sep 2018 16:56:07 -0700 Subject: [PATCH 31/65] fix memory leak, add strengthening Signed-off-by: Nikolaj Bjorner --- src/sat/ba_solver.cpp | 237 ++++++++++++++++++++++++------------ src/sat/ba_solver.h | 9 +- src/sat/sat_config.cpp | 8 ++ src/sat/sat_config.h | 6 + src/sat/sat_params.pyg | 3 +- src/sat/tactic/goal2sat.cpp | 1 + src/util/uint_set.h | 7 +- 7 files changed, 183 insertions(+), 88 deletions(-) diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index 2c33bd955..5e1febe9e 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -1065,13 +1065,14 @@ namespace sat { return c; } - void ba_solver::get_coeff(bool_var v, literal& l, unsigned& c) { + ba_solver::wliteral ba_solver::get_wliteral(bool_var v) { int64_t c1 = get_coeff(v); - l = literal(v, c1 < 0); + literal l = literal(v, c1 < 0); c1 = std::abs(c1); - c = static_cast(c1); + unsigned c = static_cast(c1); // TRACE("ba", tout << l << " " << c << "\n";); m_overflow |= c != c1; + return wliteral(c, l); } unsigned ba_solver::get_abs_coeff(bool_var v) const { @@ -1121,7 +1122,6 @@ namespace sat { // #define DEBUG_CODE(_x_) _x_ void ba_solver::bail_resolve_conflict(unsigned idx) { - m_overflow = false; literal_vector const& lits = s().m_trail; while (m_num_marks > 0) { bool_var v = lits[idx].var(); @@ -1157,13 +1157,14 @@ namespace sat { } lbool ba_solver::resolve_conflict() { -#if 1 - return resolve_conflict_rs(); -#endif - if (0 == m_num_propagations_since_pop) { return l_undef; } + + if (s().m_config.m_pb_resolve == PB_ROUNDING) { + return resolve_conflict_rs(); + } + m_overflow = false; reset_coeffs(); m_num_marks = 0; @@ -1348,6 +1349,10 @@ namespace sat { return l_true; bail_out: + if (m_overflow) { + ++m_stats.m_num_overflow; + m_overflow = false; + } bail_resolve_conflict(idx); return l_undef; } @@ -1403,24 +1408,25 @@ namespace sat { } } ineq.divide(c); + TRACE("ba", display(tout << "var: " << v << " " << c << ": ", ineq, true);); } void ba_solver::round_to_one(bool_var w) { unsigned c = get_abs_coeff(w); if (c == 1 || c == 0) return; for (bool_var v : m_active_vars) { - literal l; - unsigned ci; - get_coeff(v, l, ci); - unsigned q = ci % c; - if (q != 0 && !is_false(l)) { - m_coeffs[v] = ci - q; + wliteral wl = get_wliteral(v); + unsigned q = wl.first % c; + if (q != 0 && !is_false(wl.second)) { + m_coeffs[v] = wl.first - q; m_bound -= q; SASSERT(m_bound > 0); } } + SASSERT(validate_lemma()); divide(c); SASSERT(validate_lemma()); + TRACE("ba", active2pb(m_B); display(tout, m_B, true);); } void ba_solver::divide(unsigned c) { @@ -1431,8 +1437,7 @@ namespace sat { for (unsigned i = 0; i < sz; ++i) { bool_var v = m_active_vars[i]; int ci = get_int_coeff(v); - if (m_active_var_set.contains(v) || ci == 0) continue; - m_active_var_set.insert(v); + if (!test_and_set_active(v) || ci == 0) continue; if (ci > 0) { m_coeffs[v] = (ci + c - 1) / c; } @@ -1452,10 +1457,13 @@ namespace sat { void ba_solver::resolve_with(ineq const& ineq) { TRACE("ba", display(tout, ineq, true);); - inc_bound(ineq.m_k); + inc_bound(ineq.m_k); + TRACE("ba", tout << "bound: " << m_bound << "\n";); + for (unsigned i = ineq.size(); i-- > 0; ) { literal l = ineq.lit(i); inc_coeff(l, static_cast(ineq.coeff(i))); + TRACE("ba", tout << "bound: " << m_bound << " lit: " << l << " coeff: " << ineq.coeff(i) << "\n";); } } @@ -1518,27 +1526,26 @@ namespace sat { switch (js.get_kind()) { case justification::NONE: SASSERT(consequent != null_literal); - inc_bound(1); round_to_one(consequent.var()); + inc_bound(1); inc_coeff(consequent, 1); break; case justification::BINARY: SASSERT(consequent != null_literal); - inc_bound(1); round_to_one(consequent.var()); + inc_bound(1); inc_coeff(consequent, 1); process_antecedent(js.get_literal()); break; case justification::TERNARY: SASSERT(consequent != null_literal); - inc_bound(1); round_to_one(consequent.var()); + inc_bound(1); inc_coeff(consequent, 1); process_antecedent(js.get_literal1()); process_antecedent(js.get_literal2()); break; case justification::CLAUSE: { - inc_bound(1); clause & c = s().get_clause(js); unsigned i = 0; if (consequent != null_literal) { @@ -1553,6 +1560,7 @@ namespace sat { i = 2; } } + inc_bound(1); unsigned sz = c.size(); for (; i < sz; i++) process_antecedent(c[i]); @@ -1576,6 +1584,7 @@ namespace sat { } else { SASSERT(k > c); + TRACE("ba", tout << "visited: " << l << "\n";); k -= c; } } @@ -1590,6 +1599,7 @@ namespace sat { } mark_variables(m_A); if (consequent == null_literal) { + SASSERT(validate_ineq(m_A)); m_bound = static_cast(m_A.m_k); for (wliteral wl : m_A.m_wlits) { process_antecedent(wl.second, wl.first); @@ -1597,9 +1607,11 @@ namespace sat { } else { round_to_one(consequent.var()); - if (cnstr.tag() == pb_t) round_to_one(m_A, consequent.var()); + if (cnstr.tag() == pb_t) round_to_one(m_A, consequent.var()); + SASSERT(validate_ineq(m_A)); resolve_with(m_A); } + break; } default: UNREACHABLE(); @@ -1616,13 +1628,18 @@ namespace sat { v = consequent.var(); mark_visited(v); if (s().is_marked(v)) { - if (get_coeff(v) != 0) { + int64_t c = get_coeff(v); + if (c == 0) { + CTRACE("ba", c != 0, active2pb(m_A); display(tout << consequent << ": ", m_A, true);); + s().reset_mark(v); + --m_num_marks; + } + else { break; } - s().reset_mark(v); - --m_num_marks; } if (idx == 0) { + TRACE("ba", tout << "there is no consequent\n";); goto bail_out; } --idx; @@ -1647,9 +1664,14 @@ namespace sat { active2constraint(); return l_true; } + bail_out: - IF_VERBOSE(1, verbose_stream() << "bail\n"); - m_overflow = false; + IF_VERBOSE(1, verbose_stream() << "bail " << m_overflow << "\n"); + TRACE("ba", tout << "bail " << m_overflow << "\n";); + if (m_overflow) { + ++m_stats.m_num_overflow; + m_overflow = false; + } return l_undef; } @@ -1657,9 +1679,16 @@ namespace sat { bool ba_solver::create_asserting_lemma() { int64_t bound64 = m_bound; int64_t slack = -bound64; - for (bool_var v : m_active_vars) { - slack += get_abs_coeff(v); + reset_active_var_set(); + unsigned j = 0, sz = m_active_vars.size(); + for (unsigned i = 0; i < sz; ++i) { + bool_var v = m_active_vars[i]; + unsigned c = get_abs_coeff(v); + if (!test_and_set_active(v) || c == 0) continue; + slack += c; + m_active_vars[j++] = v; } + m_active_vars.shrink(j); m_lemma.reset(); m_lemma.push_back(null_literal); unsigned num_skipped = 0; @@ -1694,16 +1723,19 @@ namespace sat { } } if (slack >= 0) { + TRACE("ba", tout << "slack is non-negative\n";); IF_VERBOSE(20, verbose_stream() << "(sat.card slack: " << slack << " skipped: " << num_skipped << ")\n";); return false; } if (m_overflow) { + TRACE("ba", tout << "overflow\n";); return false; } if (m_lemma[0] == null_literal) { if (m_lemma.size() == 1) { s().set_conflict(justification()); } + TRACE("ba", tout << "no asserting literal\n";); return false; } @@ -1767,8 +1799,7 @@ namespace sat { for (unsigned i = 0; i < sz; ++i) { bool_var v = m_active_vars[i]; int64_t c = m_coeffs[v]; - if (m_active_var_set.contains(v) || c == 0) continue; - m_active_var_set.insert(v); + if (!test_and_set_active(v) || c == 0) continue; m_coeffs[v] /= static_cast(g); m_active_vars[j++] = v; } @@ -1834,7 +1865,8 @@ namespace sat { return p; } - ba_solver::ba_solver(): m_solver(0), m_lookahead(0), m_unit_walk(0), m_constraint_id(0), m_ba(*this), m_sort(m_ba) { + ba_solver::ba_solver(): m_solver(0), m_lookahead(0), m_unit_walk(0), + m_allocator("ba"), m_constraint_id(0), m_ba(*this), m_sort(m_ba) { TRACE("ba", tout << this << "\n";); m_num_propagations_since_pop = 0; } @@ -2649,8 +2681,12 @@ namespace sat { constraint* c = m_learned[i]; if (!m_constraint_to_reinit.contains(c)) { remove_constraint(*c, "gc"); + m_allocator.deallocate(c->obj_size(), c); ++removed; } + else { + m_learned[new_sz++] = c; + } } m_stats.m_num_gc += removed; m_learned.shrink(new_sz); @@ -3453,38 +3489,58 @@ namespace sat { } } - void ba_solver::unit_strengthen(big& big, card& c) { - for (literal l : c) { - literal r = big.get_root(~l); - if (r == ~l) continue; - unsigned k = c.k(); - for (literal u : c) { - if (big.reaches(r, ~u)) { - if (k == 0) { - // ~r + C >= c.k() + 1 - IF_VERBOSE(0, verbose_stream() << "TBD add " << ~r << " to " << c << "\n";); - return; - } - --k; + void ba_solver::unit_strengthen(big& big, pb_base& p) { + if (p.lit() != null_literal) return; + unsigned sz = p.size(); + for (unsigned i = 0; i < sz; ++i) { + literal u = p.get_lit(i); + literal r = big.get_root(u); + if (r == u) continue; + unsigned k = p.k(), b = 0; + for (unsigned j = 0; j < sz; ++j) { + literal v = p.get_lit(j); + if (r == big.get_root(v)) { + b += p.get_coeff(j); } - } - } - } - - void ba_solver::unit_strengthen(big& big, pb& p) { - for (wliteral wl : p) { - literal r = big.get_root(~wl.second); - if (r == ~wl.second) continue; - unsigned k = p.k(); - for (wliteral u : p) { - if (big.reaches(r, ~u.second)) { - if (k < u.first) { - // ~r + p >= p.k() + 1 - IF_VERBOSE(0, verbose_stream() << "TBD add " << ~r << " to " << p << "\n";); - return; + } + if (b > k) { + r.neg(); + unsigned coeff = b - k; + + svector wlits; + // add coeff * r to p + wlits.push_back(wliteral(coeff, r)); + for (unsigned j = 0; j < sz; ++j) { + u = p.get_lit(j); + unsigned c = p.get_coeff(j); + if (r == u) { + wlits[0].first += c; + } + else if (~r == u) { + if (coeff == c) { + wlits[0] = wlits.back(); + wlits.pop_back(); + b -= c; + } + else if (coeff < c) { + wlits[0].first = c - coeff; + wlits[0].second.neg(); + b -= coeff; + } + else { + // coeff > c + wlits[0].first = coeff - c; + b -= c; + } + } + else { + wlits.push_back(wliteral(c, u)); } - k -= u.first; } + ++m_stats.m_num_big_strengthenings; + p.set_removed(); + constraint* c = add_pb_ge(null_literal, wlits, b, p.learned()); + return; } } } @@ -3784,7 +3840,7 @@ namespace sat { } init_visited(); for (wliteral l : p1) { - SASSERT(m_weights[l.second.index()] == 0); + SASSERT(m_weights.get(l.second.index(), 0) == 0); m_weights.setx(l.second.index(), l.first, 0); mark_visited(l.second); } @@ -3992,7 +4048,8 @@ namespace sat { void ba_solver::display(std::ostream& out, ineq const& ineq, bool values) const { for (unsigned i = 0; i < ineq.size(); ++i) { - out << ineq.coeff(i) << "*" << ineq.lit(i) << " "; + if (ineq.coeff(i) != 1) out << ineq.coeff(i) << "*"; + out << ineq.lit(i) << " "; if (values) out << value(ineq.lit(i)) << " "; } out << ">= " << ineq.m_k << "\n"; @@ -4083,6 +4140,8 @@ namespace sat { st.update("ba resolves", m_stats.m_num_resolves); st.update("ba cuts", m_stats.m_num_cut); st.update("ba gc", m_stats.m_num_gc); + st.update("ba overflow", m_stats.m_num_overflow); + st.update("ba big strengthenings", m_stats.m_num_big_strengthenings); } bool ba_solver::validate_unit_propagation(card const& c, literal alit) const { @@ -4177,23 +4236,44 @@ namespace sat { int64_t val = -bound64; reset_active_var_set(); for (bool_var v : m_active_vars) { - if (m_active_var_set.contains(v)) continue; - unsigned coeff; - literal lit; - get_coeff(v, lit, coeff); - if (coeff == 0) continue; - if (!is_false(lit)) { - val += coeff; + if (!test_and_set_active(v)) continue; + wliteral wl = get_wliteral(v); + if (wl.first == 0) continue; + if (!is_false(wl.second)) { + val += wl.first; } } - CTRACE("ba", val >= 0, active2pb(m_A); display(tout, m_A);); + CTRACE("ba", val >= 0, active2pb(m_A); display(tout, m_A, true);); return val < 0; } + /** + * the slack of inequalities on the stack should be non-positive. + */ + bool ba_solver::validate_ineq(ineq const& ineq) const { + int64_t k = -static_cast(ineq.m_k); + for (wliteral wl : ineq.m_wlits) { + if (!is_false(wl.second)) + k += wl.first; + } + CTRACE("ba", k > 0, display(tout, ineq, true);); + return k <= 0; + } + void ba_solver::reset_active_var_set() { while (!m_active_var_set.empty()) m_active_var_set.erase(); } + bool ba_solver::test_and_set_active(bool_var v) { + if (m_active_var_set.contains(v)) { + return false; + } + else { + m_active_var_set.insert(v); + return true; + } + } + void ba_solver::active2pb(ineq& p) { p.reset(m_bound); active2wlits(p.m_wlits); @@ -4205,17 +4285,14 @@ namespace sat { } void ba_solver::active2wlits(svector& wlits) { - reset_active_var_set(); uint64_t sum = 0; + reset_active_var_set(); for (bool_var v : m_active_vars) { - if (m_active_var_set.contains(v)) continue; - unsigned coeff; - literal lit; - get_coeff(v, lit, coeff); - if (coeff == 0) continue; - m_active_var_set.insert(v); - wlits.push_back(wliteral(coeff, lit)); - sum += coeff; + if (!test_and_set_active(v)) continue; + wliteral wl = get_wliteral(v); + if (wl.first == 0) continue; + wlits.push_back(wl); + sum += wl.first; } m_overflow |= sum >= UINT_MAX/2; } diff --git a/src/sat/ba_solver.h b/src/sat/ba_solver.h index 9418cdd99..b547c2292 100644 --- a/src/sat/ba_solver.h +++ b/src/sat/ba_solver.h @@ -42,8 +42,10 @@ namespace sat { unsigned m_num_bin_subsumes; unsigned m_num_clause_subsumes; unsigned m_num_pb_subsumes; + unsigned m_num_big_strengthenings; unsigned m_num_cut; unsigned m_num_gc; + unsigned m_num_overflow; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; @@ -316,8 +318,7 @@ namespace sat { bool elim_pure(literal lit); void unit_strengthen(); void unit_strengthen(big& big, constraint& cs); - void unit_strengthen(big& big, card& c); - void unit_strengthen(big& big, pb& p); + void unit_strengthen(big& big, pb_base& p); void subsumption(constraint& c1); void subsumption(card& c1); void gc_half(char const* _method); @@ -455,10 +456,11 @@ namespace sat { mutable bool m_overflow; void reset_active_var_set(); + bool test_and_set_active(bool_var v); void inc_coeff(literal l, unsigned offset); int64_t get_coeff(bool_var v) const; uint64_t get_coeff(literal lit) const; - void get_coeff(bool_var v, literal& l, unsigned& c); + wliteral get_wliteral(bool_var v); unsigned get_abs_coeff(bool_var v) const; int get_int_coeff(bool_var v) const; unsigned get_bound() const; @@ -477,6 +479,7 @@ namespace sat { bool validate_conflict(pb const& p) const; bool validate_assign(literal_vector const& lits, literal lit); bool validate_lemma(); + bool validate_ineq(ineq const& ineq) const; bool validate_unit_propagation(card const& c, literal alit) const; bool validate_unit_propagation(pb const& p, literal alit) const; bool validate_unit_propagation(pb const& p, literal_vector const& r, literal alit) const; diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index c7f2377c9..ad83fac7a 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -196,6 +196,14 @@ namespace sat { else throw sat_param_exception("invalid PB solver: solver, totalizer, circuit, sorting, segmented"); + s = p.pb_resolve(); + if (s == "cardinality") + m_pb_resolve = PB_CARDINALITY; + else if (s == "rounding") + m_pb_resolve = PB_ROUNDING; + else + throw sat_param_exception("invalid PB resolve: 'cardinality' or 'resolve' expected"); + m_card_solver = p.cardinality_solver(); sat_simplifier_params sp(_p); diff --git a/src/sat/sat_config.h b/src/sat/sat_config.h index 37efe69ed..40ab5fa38 100644 --- a/src/sat/sat_config.h +++ b/src/sat/sat_config.h @@ -60,6 +60,11 @@ namespace sat { PB_SEGMENTED }; + enum pb_resolve { + PB_CARDINALITY, + PB_ROUNDING + }; + enum reward_t { ternary_reward, unit_literal_reward, @@ -148,6 +153,7 @@ namespace sat { pb_solver m_pb_solver; bool m_card_solver; + pb_resolve m_pb_resolve; // branching heuristic settings. branching_heuristic m_branching_heuristic; diff --git a/src/sat/sat_params.pyg b/src/sat/sat_params.pyg index 89776c479..7c289a900 100644 --- a/src/sat/sat_params.pyg +++ b/src/sat/sat_params.pyg @@ -42,8 +42,9 @@ def_module_params('sat', ('drat.check_sat', BOOL, False, 'build up internal trace, check satisfying model'), ('cardinality.solver', BOOL, True, 'use cardinality solver'), ('pb.solver', SYMBOL, 'solver', 'method for handling Pseudo-Boolean constraints: circuit (arithmetical circuit), sorting (sorting circuit), totalizer (use totalizer encoding), solver (use native solver)'), - ('xor.solver', BOOL, False, 'use xor solver'), + ('xor.solver', BOOL, False, 'use xor solver'), ('cardinality.encoding', SYMBOL, 'grouped', 'encoding used for at-most-k constraints: grouped, bimander, ordered, unate, circuit'), + ('pb.resolve', SYMBOL, 'cardinality', 'resolution strategy for boolean algebra solver: cardinality, rounding'), ('local_search', BOOL, False, 'use local search instead of CDCL'), ('local_search_threads', UINT, 0, 'number of local search threads to find satisfiable solution'), ('local_search_mode', SYMBOL, 'wsat', 'local search algorithm, either default wsat or qsat'), diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index 7faa89370..9d0d29ac0 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -82,6 +82,7 @@ struct goal2sat::imp { m_is_lemma(false) { updt_params(p); m_true = sat::null_bool_var; + mk_true(); } void updt_params(params_ref const & p) { diff --git a/src/util/uint_set.h b/src/util/uint_set.h index 0f3715cb1..e6518b435 100644 --- a/src/util/uint_set.h +++ b/src/util/uint_set.h @@ -269,10 +269,9 @@ public: void remove(unsigned v) { if (contains(v)) { m_in_set[v] = false; - unsigned i = 0; - for (i = 0; i < m_set.size() && m_set[i] != v; ++i) - ; - SASSERT(i < m_set.size()); + unsigned i = m_set.size(); + for (; i > 0 && m_set[--i] != v; ) ; + SASSERT(m_set[i] == v); m_set[i] = m_set.back(); m_set.pop_back(); } From 9ad17296c2e49b078c4ea343e58944f5f675b692 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 3 Sep 2018 17:22:48 -0700 Subject: [PATCH 32/65] update parameters Signed-off-by: Nikolaj Bjorner --- src/sat/ba_solver.cpp | 35 +++++++++++++++++++++-------------- src/sat/ba_solver.h | 2 ++ src/sat/sat_config.cpp | 8 ++++++++ src/sat/sat_config.h | 6 ++++++ src/sat/sat_params.pyg | 1 + 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index 5e1febe9e..f6c544cdf 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -1341,7 +1341,7 @@ namespace sat { if (!create_asserting_lemma()) { goto bail_out; } - active2card(); + active2lemma(); DEBUG_CODE(VERIFY(validate_conflict(m_lemma, m_A));); @@ -1654,19 +1654,16 @@ namespace sat { while (m_num_marks > 0 && !m_overflow); TRACE("ba", active2pb(m_A); display(tout, m_A, true);); -#if 0 - // why this? + // TBD: check if this is useful if (!m_overflow && consequent != null_literal) { round_to_one(consequent.var()); } -#endif if (!m_overflow && create_asserting_lemma()) { - active2constraint(); + active2lemma(); return l_true; } bail_out: - IF_VERBOSE(1, verbose_stream() << "bail " << m_overflow << "\n"); TRACE("ba", tout << "bail " << m_overflow << "\n";); if (m_overflow) { ++m_stats.m_num_overflow; @@ -4142,6 +4139,8 @@ namespace sat { st.update("ba gc", m_stats.m_num_gc); st.update("ba overflow", m_stats.m_num_overflow); st.update("ba big strengthenings", m_stats.m_num_big_strengthenings); + st.update("ba lemmas", m_stats.m_num_lemmas); + st.update("ba subsumes", m_stats.m_num_bin_subsumes + m_stats.m_num_clause_subsumes + m_stats.m_num_pb_subsumes); } bool ba_solver::validate_unit_propagation(card const& c, literal alit) const { @@ -4297,6 +4296,18 @@ namespace sat { m_overflow |= sum >= UINT_MAX/2; } + ba_solver::constraint* ba_solver::active2lemma() { + switch (s().m_config.m_pb_lemma_format) { + case PB_LEMMA_CARDINALITY: + return active2card(); + case PB_LEMMA_PB: + return active2constraint(); + default: + UNREACHABLE(); + return nullptr; + } + } + ba_solver::constraint* ba_solver::active2constraint() { active2wlits(); if (m_overflow) { @@ -4304,6 +4315,7 @@ namespace sat { } constraint* c = add_pb_ge(null_literal, m_wlits, m_bound, true); TRACE("ba", if (c) display(tout, *c, true);); + ++m_stats.m_num_lemmas; return c; } @@ -4381,13 +4393,7 @@ namespace sat { } if (slack >= k) { -#if 0 - return active2constraint(); - active2pb(m_A); - std::cout << "not asserting\n"; - display(std::cout, m_A, true); -#endif - return 0; + return nullptr; } // produce asserting cardinality constraint @@ -4397,8 +4403,9 @@ namespace sat { } constraint* c = add_at_least(null_literal, lits, k, true); + ++m_stats.m_num_lemmas; + if (c) { - // IF_VERBOSE(0, verbose_stream() << *c << "\n";); lits.reset(); for (wliteral wl : m_wlits) { if (value(wl.second) == l_false) lits.push_back(wl.second); diff --git a/src/sat/ba_solver.h b/src/sat/ba_solver.h index b547c2292..8f9488167 100644 --- a/src/sat/ba_solver.h +++ b/src/sat/ba_solver.h @@ -46,6 +46,7 @@ namespace sat { unsigned m_num_cut; unsigned m_num_gc; unsigned m_num_overflow; + unsigned m_num_lemmas; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; @@ -498,6 +499,7 @@ namespace sat { ineq m_A, m_B, m_C; void active2pb(ineq& p); + constraint* active2lemma(); constraint* active2constraint(); constraint* active2card(); void active2wlits(); diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index ad83fac7a..a864579f8 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -204,6 +204,14 @@ namespace sat { else throw sat_param_exception("invalid PB resolve: 'cardinality' or 'resolve' expected"); + s = p.pb_lemma_format(); + if (s == "cardinality") + m_pb_lemma_format = PB_LEMMA_CARDINALITY; + else if (s == "pb") + m_pb_lemma_format = PB_LEMMA_PB; + else + throw sat_param_exception("invalid PB lemma format: 'cardinality' or 'pb' expected"); + m_card_solver = p.cardinality_solver(); sat_simplifier_params sp(_p); diff --git a/src/sat/sat_config.h b/src/sat/sat_config.h index 40ab5fa38..b5cfb9e28 100644 --- a/src/sat/sat_config.h +++ b/src/sat/sat_config.h @@ -65,6 +65,11 @@ namespace sat { PB_ROUNDING }; + enum pb_lemma_format { + PB_LEMMA_CARDINALITY, + PB_LEMMA_PB + }; + enum reward_t { ternary_reward, unit_literal_reward, @@ -154,6 +159,7 @@ namespace sat { pb_solver m_pb_solver; bool m_card_solver; pb_resolve m_pb_resolve; + pb_lemma_format m_pb_lemma_format; // branching heuristic settings. branching_heuristic m_branching_heuristic; diff --git a/src/sat/sat_params.pyg b/src/sat/sat_params.pyg index 7c289a900..390cdfa53 100644 --- a/src/sat/sat_params.pyg +++ b/src/sat/sat_params.pyg @@ -45,6 +45,7 @@ def_module_params('sat', ('xor.solver', BOOL, False, 'use xor solver'), ('cardinality.encoding', SYMBOL, 'grouped', 'encoding used for at-most-k constraints: grouped, bimander, ordered, unate, circuit'), ('pb.resolve', SYMBOL, 'cardinality', 'resolution strategy for boolean algebra solver: cardinality, rounding'), + ('pb.lemma_format', SYMBOL, 'cardinality', 'generate either cardinality or pb lemmas'), ('local_search', BOOL, False, 'use local search instead of CDCL'), ('local_search_threads', UINT, 0, 'number of local search threads to find satisfiable solution'), ('local_search_mode', SYMBOL, 'wsat', 'local search algorithm, either default wsat or qsat'), From f53b7aaca2e1882f0e835947ceb24ae9533a7f41 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 4 Sep 2018 15:46:10 -0700 Subject: [PATCH 33/65] fix none-case Signed-off-by: Nikolaj Bjorner --- src/sat/ba_solver.cpp | 5 ++--- src/sat/sat_config.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index f6c544cdf..acc3bbd21 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -1607,7 +1607,7 @@ namespace sat { } else { round_to_one(consequent.var()); - if (cnstr.tag() == pb_t) round_to_one(m_A, consequent.var()); + if (cnstr.is_pb()) round_to_one(m_A, consequent.var()); SASSERT(validate_ineq(m_A)); resolve_with(m_A); } @@ -1629,8 +1629,7 @@ namespace sat { mark_visited(v); if (s().is_marked(v)) { int64_t c = get_coeff(v); - if (c == 0) { - CTRACE("ba", c != 0, active2pb(m_A); display(tout << consequent << ": ", m_A, true);); + if (c == 0 || (c < 0 == consequent.sign())) { s().reset_mark(v); --m_num_marks; } diff --git a/src/sat/sat_config.cpp b/src/sat/sat_config.cpp index a864579f8..07aacd9ee 100644 --- a/src/sat/sat_config.cpp +++ b/src/sat/sat_config.cpp @@ -202,7 +202,7 @@ namespace sat { else if (s == "rounding") m_pb_resolve = PB_ROUNDING; else - throw sat_param_exception("invalid PB resolve: 'cardinality' or 'resolve' expected"); + throw sat_param_exception("invalid PB resolve: 'cardinality' or 'rounding' expected"); s = p.pb_lemma_format(); if (s == "cardinality") From 13abf5c6a6dec1a364cb335f497b92593b7481fb Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 6 Sep 2018 17:49:52 -0700 Subject: [PATCH 34/65] n/a Signed-off-by: Nikolaj Bjorner --- src/sat/sat_solver.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 7ddc80813..065dc1d39 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -340,12 +340,6 @@ namespace sat { } void solver::mk_bin_clause(literal l1, literal l2, bool learned) { -#if 0 - if ((l1.var() == 2039 || l2.var() == 2039) && - (l1.var() == 27042 || l2.var() == 27042)) { - IF_VERBOSE(1, verbose_stream() << "mk_bin: " << l1 << " " << l2 << " " << learned << "\n"); - } -#endif if (find_binary_watch(get_wlist(~l1), ~l2)) { assign(l1, justification()); return; From 386cbade89415ff9328e08e31e4751c421f91b46 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 7 Sep 2018 16:06:17 -0700 Subject: [PATCH 35/65] na Signed-off-by: Nikolaj Bjorner --- src/smt/theory_jobscheduler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp index 918f349cd..a372a274f 100644 --- a/src/smt/theory_jobscheduler.cpp +++ b/src/smt/theory_jobscheduler.cpp @@ -86,7 +86,6 @@ namespace smt { return true; } - // TBD: stronger parameter validation void theory_jobscheduler::internalize_cmd(expr* cmd) { symbol key, val; rational r; From 445546b684ceb512330e25f2a569dc11c0f05e6a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 10 Sep 2018 17:20:40 -0700 Subject: [PATCH 36/65] fix gc Signed-off-by: Nikolaj Bjorner --- src/sat/ba_solver.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index acc3bbd21..88de3b6a1 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -1570,6 +1570,7 @@ namespace sat { ++m_stats.m_num_resolves; ext_justification_idx index = js.get_ext_justification_idx(); constraint& cnstr = index2constraint(index); + SASSERT(!cnstr.was_removed()); switch (cnstr.tag()) { case card_t: case pb_t: { @@ -2660,7 +2661,8 @@ namespace sat { } void ba_solver::gc() { - if (m_learned.size() >= 2 * m_constraints.size()) { + if (m_learned.size() >= 2 * m_constraints.size() && + (s().at_search_lvl() || s().at_base_lvl())) { for (auto & c : m_learned) update_psm(*c); std::stable_sort(m_learned.begin(), m_learned.end(), constraint_glue_psm_lt()); gc_half("glue-psm"); From 77e43404703f2bc37f4b7579c7f56905d4ab2eca Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 14 Oct 2018 13:06:09 -0700 Subject: [PATCH 37/65] update parser Signed-off-by: Nikolaj Bjorner --- src/ast/csp_decl_plugin.cpp | 55 +++++++++++++++++++++++++++++++-- src/ast/csp_decl_plugin.h | 16 +++++++++- src/parsers/smt2/smt2parser.cpp | 6 ---- 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/ast/csp_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp index 27feadc6d..9cdd20376 100644 --- a/src/ast/csp_decl_plugin.cpp +++ b/src/ast/csp_decl_plugin.cpp @@ -119,7 +119,8 @@ func_decl * csp_decl_plugin::mk_func_decl( rng = m_alist_sort; break; case OP_JS_JOB_PREEMPTABLE: - if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("set-preemptable expects one argument, which is a job"); + if (arity != 1 || domain[0] != m_job_sort) + m_manager->raise_exception("set-preemptable expects one argument, which is a job"); name = symbol("set-preemptable"); rng = m_alist_sort; break; @@ -131,6 +132,22 @@ func_decl * csp_decl_plugin::mk_func_decl( name = symbol("js-properties"); rng = m_alist_sort; break; + case OP_JS_JOB_GOAL: + if (arity != 1 || domain[0] != m_job_sort) + m_manager->raise_exception("add-job-goal expects one argument, which is a job"); + if (num_parameters != 2 || !parameters[0].is_symbol() || !parameters[1].is_int()) + m_manager->raise_exception("add-job-goal expects one symbol and one integer parameter"); + name = symbol("add-job-goal"); + rng = m_alist_sort; + break; + case OP_JS_OBJECTIVE: + if (arity != 0) + m_manager->raise_exception("add-optimization-objective expects no arguments"); + if (num_parameters != 1 || !parameters[0].is_symbol()) + m_manager->raise_exception("add-optimization-objective expects one symbol parameter"); + name = symbol("add-optimization-objective"); + rng = m_alist_sort; + break; default: UNREACHABLE(); return nullptr; @@ -172,6 +189,8 @@ void csp_decl_plugin::get_op_names(svector & op_names, symbol cons op_names.push_back(builtin_name("add-resource-available", OP_JS_RESOURCE_AVAILABLE)); op_names.push_back(builtin_name("set-preemptable", OP_JS_JOB_PREEMPTABLE)); op_names.push_back(builtin_name("js-properties", OP_JS_PROPERTIES)); + op_names.push_back(builtin_name("add-job-goal", OP_JS_JOB_GOAL)); + op_names.push_back(builtin_name("add-optimization-objective", OP_JS_OBJECTIVE)); } } @@ -309,7 +328,8 @@ bool csp_util::is_set_preemptable(expr* e, expr *& job) { } bool csp_util::is_js_properties(expr* e, svector& properties) { - if (!is_app_of(e, m_fid, OP_JS_PROPERTIES)) return false; + if (!is_app_of(e, m_fid, OP_JS_PROPERTIES)) + return false; unsigned sz = to_app(e)->get_decl()->get_num_parameters(); for (unsigned i = 0; i < sz; ++i) { properties.push_back(to_app(e)->get_decl()->get_parameter(i).get_symbol()); @@ -317,4 +337,35 @@ bool csp_util::is_js_properties(expr* e, svector& properties) { return true; } +bool csp_util::is_job_goal(expr* e, js_job_goal& goal, unsigned& level, expr*& job) { + if (!is_app_of(e, m_fid, OP_JS_JOB_GOAL)) + return false; + SASSERT(2 == to_app(e)->get_decl()->get_num_parameters()); + SASSERT(1 == to_app(e)->get_num_args()); + symbol g = to_app(e)->get_decl()->get_parameter(0).get_symbol(); + level = to_app(e)->get_decl()->get_parameter(1).get_int(); + if (g == ":earliest-end-time" || g == "earliest-end-time") + goal = JS_JOB_GOAL_EARLIEST_END_TIME; + else if (g == ":latest-start-time" || g == "latest-start-time") + goal = JS_JOB_GOAL_LATEST_START_TIME; + else + return false; + job = to_app(e)->get_arg(0); + return true; +} + +bool csp_util::is_objective(expr* e, js_optimization_objective& objective) { + if (!is_app_of(e, m_fid, OP_JS_OBJECTIVE)) + return false; + SASSERT(1 == to_app(e)->get_decl()->get_num_parameters()); + symbol obj = to_app(e)->get_decl()->get_parameter(0).get_symbol(); + if (obj == ":duration" || obj == "duration") + objective = JS_OBJECTIVE_DURATION; + else if (obj == ":priority" || obj == "priority") + objective = JS_OBJECTIVE_PRIORITY; + else + return false; + return true; +} + diff --git a/src/ast/csp_decl_plugin.h b/src/ast/csp_decl_plugin.h index 8d6ad56fa..05d6bfa7e 100644 --- a/src/ast/csp_decl_plugin.h +++ b/src/ast/csp_decl_plugin.h @@ -83,7 +83,19 @@ enum js_op_kind { OP_JS_JOB_RESOURCE, // model declaration for job assignment to resource OP_JS_JOB_PREEMPTABLE, // model declaration for whether job is pre-emptable OP_JS_RESOURCE_AVAILABLE, // model declaration for availability intervals of resource - OP_JS_PROPERTIES // model declaration of a set of properties. Each property is a keyword. + OP_JS_PROPERTIES, // model declaration of a set of properties. Each property is a keyword. + OP_JS_JOB_GOAL, // job goal objective :earliest-end-time or :latest-start-time + OP_JS_OBJECTIVE // duration or completion-time +}; + +enum js_job_goal { + JS_JOB_GOAL_EARLIEST_END_TIME, + JS_JOB_GOAL_LATEST_START_TIME +}; + +enum js_optimization_objective { + JS_OBJECTIVE_DURATION, + JS_OBJECTIVE_PRIORITY }; class csp_decl_plugin : public decl_plugin { @@ -140,6 +152,8 @@ public: bool is_set_preemptable(expr* e, expr *& job); bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } bool is_js_properties(expr* e, svector& properties); + bool is_job_goal(expr* e, js_job_goal& goal, unsigned& level, expr*& job); + bool is_objective(expr* e, js_optimization_objective& objective); private: unsigned job2id(expr* j); diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index d1b4847cc..fe7c048a9 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -625,8 +625,6 @@ namespace smt2 { args.push_back(u); next(); } - if (args.empty()) - throw parser_exception("invalid indexed sort, index expected"); sort * r = d->instantiate(pm(), args.size(), args.c_ptr()); if (r == nullptr) throw parser_exception("invalid sort application"); @@ -1520,7 +1518,6 @@ namespace smt2 { check_identifier("invalid indexed identifier, symbol expected"); symbol r = curr_id(); next(); - unsigned num_indices = 0; while (!curr_is_rparen()) { if (curr_is_int()) { if (!curr_numeral().is_unsigned()) { @@ -1545,10 +1542,7 @@ namespace smt2 { else { throw parser_exception("invalid indexed identifier, integer, identifier or '(' expected"); } - num_indices++; } - if (num_indices == 0) - throw parser_exception("invalid indexed identifier, index expected"); next(); return r; } From 9dd9d5e18ab9735001782e386cca0ce59bc8dbee Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 17 Oct 2018 05:22:43 -0700 Subject: [PATCH 38/65] more integration Signed-off-by: Nikolaj Bjorner --- src/smt/smt_context.h | 17 ++++--- src/smt/theory_recfun.cpp | 94 ++++++++++++++++++++++++--------------- src/smt/theory_recfun.h | 4 +- 3 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 5049eb3fc..a349880e3 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1614,23 +1614,22 @@ namespace smt { void insert_macro(func_decl * f, quantifier * m, proof * pr, expr_dependency * dep) { m_asserted_formulas.insert_macro(f, m, pr, dep); } }; - struct pp_lit { - smt::context & ctx; - smt::literal lit; - pp_lit(smt::context & ctx, smt::literal lit) : ctx(ctx), lit(lit) {} + context & ctx; + literal lit; + pp_lit(context & ctx, literal lit) : ctx(ctx), lit(lit) {} }; inline std::ostream & operator<<(std::ostream & out, pp_lit const & pp) { - pp.ctx.display_detailed_literal(out, pp.lit); - return out; + return pp.ctx.display_detailed_literal(out, pp.lit); } struct pp_lits { - smt::context & ctx; - smt::literal *lits; + context & ctx; + literal const *lits; unsigned len; - pp_lits(smt::context & ctx, unsigned len, smt::literal *lits) : ctx(ctx), lits(lits), len(len) {} + pp_lits(context & ctx, unsigned len, literal const *lits) : ctx(ctx), lits(lits), len(len) {} + pp_lits(context & ctx, literal_vector const& ls) : ctx(ctx), lits(ls.c_ptr()), len(ls.size()) {} }; inline std::ostream & operator<<(std::ostream & out, pp_lits const & pp) { diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index dcb8ad097..b108512ba 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -1,11 +1,29 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation, Simon Cuares + +Module Name: + + theory_recfun.cpp + +Abstract: + + Theory responsible for unrolling recursive functions + +Author: + + Simon Cuares December 2017 + +Revision History: + +--*/ #include "util/stats.h" #include "ast/ast_util.h" #include "smt/theory_recfun.h" #include "smt/params/smt_params_helper.hpp" -#define DEBUG(x) TRACE("recfun", tout << x << '\n';) +#define TRACEFN(x) TRACE("recfun", tout << x << '\n';) namespace smt { @@ -14,7 +32,11 @@ namespace smt { m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), m_trail(*this), - m_guards(), m_max_depth(0), m_q_case_expand(), m_q_body_expand(), m_q_clauses() + m_guards(), + m_max_depth(0), + m_q_case_expand(), + m_q_body_expand(), + m_q_clauses() { } @@ -40,13 +62,13 @@ namespace smt { bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { context & ctx = get_context(); - if (! ctx.e_internalized(atom)) { - unsigned num_args = atom->get_num_args(); - for (unsigned i = 0; i < num_args; ++i) - ctx.internalize(atom->get_arg(i), false); + for (expr * arg : *atom) { + ctx.internalize(arg, false); + } + if (!ctx.e_internalized(atom)) { ctx.mk_enode(atom, false, true, false); } - if (! ctx.b_internalized(atom)) { + if (!ctx.b_internalized(atom)) { bool_var v = ctx.mk_bool_var(atom); ctx.set_var_theory(v, get_id()); } @@ -55,12 +77,14 @@ namespace smt { bool theory_recfun::internalize_term(app * term) { context & ctx = get_context(); - for (expr* e : *term) ctx.internalize(e, false); + for (expr* e : *term) { + ctx.internalize(e, false); + } // the internalization of the arguments may have triggered the internalization of term. - if (ctx.e_internalized(term)) - return true; - ctx.mk_enode(term, false, false, true); - return true; // the theory doesn't actually map terms to variables + if (!ctx.e_internalized(term)) { + ctx.mk_enode(term, false, false, true); + } + return true; } void theory_recfun::reset_queues() { @@ -77,35 +101,34 @@ namespace smt { } /* - * when `n` becomes relevant, if it's `f(t1…tn)` with `f` defined, + * when `n` becomes relevant, if it's `f(t1...tn)` with `f` defined, * then case-expand `n`. If it's a macro we can also immediately * body-expand it. */ void theory_recfun::relevant_eh(app * n) { SASSERT(get_context().relevancy()); if (u().is_defined(n)) { - DEBUG("relevant_eh: (defined) " << mk_pp(n, m())); - + TRACEFN("relevant_eh: (defined) " << mk_pp(n, m())); case_expansion e(u(), n); push_case_expand(std::move(e)); } } void theory_recfun::push_scope_eh() { - DEBUG("push_scope"); + TRACEFN("push_scope"); theory::push_scope_eh(); m_trail.push_scope(); } void theory_recfun::pop_scope_eh(unsigned num_scopes) { - DEBUG("pop_scope " << num_scopes); + TRACEFN("pop_scope " << num_scopes); m_trail.pop_scope(num_scopes); theory::pop_scope_eh(num_scopes); reset_queues(); } void theory_recfun::restart_eh() { - DEBUG("restart"); + TRACEFN("restart"); reset_queues(); theory::restart_eh(); } @@ -120,7 +143,7 @@ namespace smt { context & ctx = get_context(); for (literal_vector & c : m_q_clauses) { - DEBUG("add axiom " << pp_lits(ctx, c.size(), c.c_ptr())); + TRACEFN("add axiom " << pp_lits(ctx, c)); ctx.mk_th_axiom(get_id(), c.size(), c.c_ptr()); } m_q_clauses.clear(); @@ -145,7 +168,7 @@ namespace smt { } void theory_recfun::max_depth_conflict() { - DEBUG("max-depth conflict"); + TRACEFN("max-depth conflict"); context & ctx = get_context(); literal_vector c; // make clause `depth_limit => V_{g : guards} ~ g` @@ -160,20 +183,20 @@ namespace smt { expr * g = & kv.get_key(); c.push_back(~ ctx.get_literal(g)); } - DEBUG("max-depth limit: add clause " << pp_lits(ctx, c.size(), c.c_ptr())); + TRACEFN("max-depth limit: add clause " << pp_lits(ctx, c)); SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict m_q_clauses.push_back(std::move(c)); } - // if `is_true` and `v = C_f_i(t1…tn)`, then body-expand i-th case of `f(t1…tn)` + // if `is_true` and `v = C_f_i(t1...tn)`, then body-expand i-th case of `f(t1…tn)` void theory_recfun::assign_eh(bool_var v, bool is_true) { expr* e = get_context().bool_var2expr(v); if (!is_true) return; if (!is_app(e)) return; app* a = to_app(e); if (u().is_case_pred(a)) { - DEBUG("assign_case_pred_true "<< mk_pp(e,m())); + TRACEFN("assign_case_pred_true "<< mk_pp(e,m())); // add to set of local assumptions, for depth-limit purpose { m_guards.insert(e, empty()); @@ -207,20 +230,19 @@ namespace smt { } app_ref theory_recfun::apply_pred(recfun::case_pred const & p, - ptr_vector const & args){ - app_ref res(u().mk_case_pred(p, args), m()); - return res; + ptr_vector const & args) { + return app_ref(u().mk_case_pred(p, args), m()); } void theory_recfun::assert_macro_axiom(case_expansion & e) { - DEBUG("assert_macro_axiom " << pp_case_expansion(e,m())); + TRACEFN("assert_macro_axiom " << pp_case_expansion(e,m())); SASSERT(e.m_def->is_fun_macro()); expr_ref lhs(e.m_lhs, m()); context & ctx = get_context(); auto & vars = e.m_def->get_vars(); // substitute `e.args` into the macro RHS expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m()); - DEBUG("macro expansion yields" << mk_pp(rhs,m())); + TRACEFN("macro expansion yields" << mk_pp(rhs,m())); // now build the axiom `lhs = rhs` ctx.internalize(rhs, false); // add unit clause `lhs=rhs` @@ -228,12 +250,12 @@ namespace smt { ctx.mark_as_relevant(l); literal_vector lits; lits.push_back(l); - DEBUG("assert_macro_axiom: " << pp_lits(ctx, lits.size(), lits.c_ptr())); + TRACEFN("assert_macro_axiom: " << pp_lits(ctx, lits)); ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); } void theory_recfun::assert_case_axioms(case_expansion & e) { - DEBUG("assert_case_axioms "<< pp_case_expansion(e,m()) + TRACEFN("assert_case_axioms "<< pp_case_expansion(e,m()) << " with " << e.m_def->get_cases().size() << " cases"); SASSERT(e.m_def->is_fun_defined()); context & ctx = get_context(); @@ -264,7 +286,7 @@ namespace smt { //TRACE("recfun", tout << "assert_case_axioms " << pp_case_expansion(e) // << " axiom " << mk_pp(*l) <<"\n";); - DEBUG("assert_case_axiom " << pp_lits(get_context(), path.size()+1, c.c_ptr())); + TRACEFN("assert_case_axiom " << pp_lits(get_context(), path.size()+1, c.c_ptr())); get_context().mk_th_axiom(get_id(), path.size()+1, c.c_ptr()); } { @@ -274,7 +296,7 @@ namespace smt { literal g = ctx.get_literal(_g); literal c[2] = {~ concl, g}; - DEBUG("assert_case_axiom " << pp_lits(get_context(), 2, c)); + TRACEFN("assert_case_axiom " << pp_lits(get_context(), 2, c)); get_context().mk_th_axiom(get_id(), 2, c); } } @@ -288,7 +310,7 @@ namespace smt { } void theory_recfun::assert_body_axiom(body_expansion & e) { - DEBUG("assert_body_axioms "<< pp_body_expansion(e,m())); + TRACEFN("assert_body_axioms "<< pp_body_expansion(e,m())); context & ctx = get_context(); recfun::def & d = *e.m_cdef->get_def(); auto & vars = d.get_vars(); @@ -320,7 +342,7 @@ namespace smt { literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); clause.push_back(l); - DEBUG("assert_body_axiom " << pp_lits(ctx, clause.size(), clause.c_ptr())); + TRACEFN("assert_body_axiom " << pp_lits(ctx, clause)); ctx.mk_th_axiom(get_id(), clause.size(), clause.c_ptr()); } @@ -330,7 +352,7 @@ namespace smt { void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); - DEBUG("add_theory_assumption " << mk_pp(dlimit.get(), m())); + TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m())); assumptions.push_back(dlimit); } @@ -354,7 +376,6 @@ namespace smt { st.update("recfun body expansion", m_stats.m_body_expansions); } -#ifdef Z3DEBUG std::ostream& operator<<(std::ostream & out, theory_recfun::pp_case_expansion const & e) { return out << "case_exp(" << mk_pp(e.e.m_lhs, e.m) << ")"; } @@ -366,5 +387,4 @@ namespace smt { } return out << ")"; } -#endif } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index a1d9048c1..68833be63 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -1,5 +1,5 @@ /*++ -Copyright (c) 2006 Microsoft Corporation +Copyright (c) 2018 Microsoft Corporation Module Name: @@ -11,7 +11,7 @@ Abstract: Author: - Leonardo de Moura (leonardo) 2008-10-31. + Simon Cuares December 2017 Revision History: From 48cdd12a471193ea87c7ca22ba16f0c3dcd2e5c6 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 17 Oct 2018 05:56:04 -0700 Subject: [PATCH 39/65] cleanup Signed-off-by: Nikolaj Bjorner --- src/smt/theory_recfun.cpp | 85 +++++++++++++-------------------------- 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index b108512ba..45902c3dd 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -189,11 +189,10 @@ namespace smt { m_q_clauses.push_back(std::move(c)); } - // if `is_true` and `v = C_f_i(t1...tn)`, then body-expand i-th case of `f(t1…tn)` + // if `is_true` and `v = C_f_i(t1...tn)`, then body-expand i-th case of `f(t1...tn)` void theory_recfun::assign_eh(bool_var v, bool is_true) { expr* e = get_context().bool_var2expr(v); - if (!is_true) return; - if (!is_app(e)) return; + if (!is_true || !is_app(e)) return; app* a = to_app(e); if (u().is_case_pred(a)) { TRACEFN("assign_case_pred_true "<< mk_pp(e,m())); @@ -235,23 +234,19 @@ namespace smt { } void theory_recfun::assert_macro_axiom(case_expansion & e) { - TRACEFN("assert_macro_axiom " << pp_case_expansion(e,m())); + TRACEFN("assert_macro_axiom " << pp_case_expansion(e, m())); SASSERT(e.m_def->is_fun_macro()); - expr_ref lhs(e.m_lhs, m()); context & ctx = get_context(); auto & vars = e.m_def->get_vars(); + expr_ref lhs(e.m_lhs, m()); // substitute `e.args` into the macro RHS expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m()); TRACEFN("macro expansion yields" << mk_pp(rhs,m())); - // now build the axiom `lhs = rhs` - ctx.internalize(rhs, false); - // add unit clause `lhs=rhs` + // add unit clause `lhs = rhs` literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); - literal_vector lits; - lits.push_back(l); - TRACEFN("assert_macro_axiom: " << pp_lits(ctx, lits)); - ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); + TRACEFN("assert_macro_axiom: " << pp_lit(ctx, l)); + ctx.mk_th_axiom(get_id(), 1, &l); } void theory_recfun::assert_case_axioms(case_expansion & e) { @@ -263,43 +258,27 @@ namespace smt { auto & vars = e.m_def->get_vars(); for (recfun::case_def const & c : e.m_def->get_cases()) { // applied predicate to `args` + literal_vector guards; + for (auto & g : c.get_guards()) { + expr_ref guard = apply_args(vars, e.m_args, g); + ctx.internalize(guard, false); + guards.push_back(~ctx.get_literal(guard)); + } app_ref pred_applied = apply_pred(c.get_pred(), e.m_args); SASSERT(u().owns_app(pred_applied)); - // substitute arguments in `path` - expr_ref_vector path(m()); - for (auto & g : c.get_guards()) { - expr_ref g_applied = apply_args(vars, e.m_args, g); - path.push_back(g_applied); - } - // assert `p(args) <=> And(guards)` (with CNF on the fly) ctx.internalize(pred_applied, false); - ctx.mark_as_relevant(ctx.get_bool_var(pred_applied)); literal concl = ctx.get_literal(pred_applied); - { - // assert `guards=>p(args)` - literal_vector c; - c.push_back(concl); - for (expr* g : path) { - ctx.internalize(g, false); - c.push_back(~ ctx.get_literal(g)); - } + ctx.mark_as_relevant(concl); - //TRACE("recfun", tout << "assert_case_axioms " << pp_case_expansion(e) - // << " axiom " << mk_pp(*l) <<"\n";); - TRACEFN("assert_case_axiom " << pp_lits(get_context(), path.size()+1, c.c_ptr())); - get_context().mk_th_axiom(get_id(), path.size()+1, c.c_ptr()); - } - { - // assert `p(args) => guards[i]` for each `i` - for (expr * _g : path) { - SASSERT(ctx.b_internalized(_g)); - literal g = ctx.get_literal(_g); - literal c[2] = {~ concl, g}; + // assert `p(args) <=> And(guards)` (with CNF on the fly) - TRACEFN("assert_case_axiom " << pp_lits(get_context(), 2, c)); - get_context().mk_th_axiom(get_id(), 2, c); - } + for (literal g : guards) { + literal c[2] = {~ concl, ~g}; + ctx.mark_as_relevant(g); + get_context().mk_th_axiom(get_id(), 2, c); } + guards.push_back(concl); + get_context().mk_th_axiom(get_id(), guards.size(), guards.c_ptr()); // also body-expand paths that do not depend on any defined fun if (c.is_immediate()) { @@ -322,23 +301,17 @@ namespace smt { expr_ref rhs = apply_args(vars, args, e.m_cdef->get_rhs()); // substitute `e.args` into the guard of this particular case, to make // the `condition` part of the clause `conds => lhs=rhs` - expr_ref_vector guards(m()); - for (auto & g : e.m_cdef->get_guards()) { - expr_ref new_guard = apply_args(vars, args, g); - guards.push_back(new_guard); - } - // now build the axiom `conds => lhs = rhs` - ctx.internalize(rhs, false); - for (auto& g : guards) ctx.internalize(g, false); - - // add unit clause `conds => lhs=rhs` + + // now build the axiom `conds => lhs = rhs` + literal_vector clause; - for (auto& g : guards) { - ctx.internalize(g, false); - literal l = ~ ctx.get_literal(g); + for (auto & g : e.m_cdef->get_guards()) { + expr_ref guard = apply_args(vars, args, g); + ctx.internalize(guard, false); + literal l = ~ ctx.get_literal(guard); ctx.mark_as_relevant(l); clause.push_back(l); - } + } literal l(mk_eq(lhs, rhs, true)); ctx.mark_as_relevant(l); clause.push_back(l); From 8a9837a8b575cd2da7219ff4afd66643b3f88c0f Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 17 Oct 2018 10:02:56 -0700 Subject: [PATCH 40/65] more refinements for recfun Signed-off-by: Nikolaj Bjorner --- src/smt/smt_theory.h | 2 ++ src/smt/theory_recfun.cpp | 70 +++++++++++++++++++-------------------- src/smt/theory_recfun.h | 2 ++ 3 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index b791d890e..fa0ec0c82 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -294,6 +294,8 @@ namespace smt { SASSERT(m_context); return *m_context; } + + context & ctx() const { return get_context(); } ast_manager & get_manager() const { SASSERT(m_manager); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 45902c3dd..23d9e7f2c 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -52,7 +52,7 @@ namespace smt { void theory_recfun::setup_params() { // obtain max depth via parameters - smt_params_helper p(get_context().get_params()); + smt_params_helper p(ctx().get_params()); set_max_depth(p.recfun_max_depth()); } @@ -175,13 +175,11 @@ namespace smt { { // first literal must be the depth limit one app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); - ctx.internalize(dlimit, false); - c.push_back(~ ctx.get_literal(dlimit)); - SASSERT(ctx.get_assignment(ctx.get_literal(dlimit)) == l_true); + c.push_back(~mk_literal(dlimit)); + SASSERT(ctx.get_assignment(c.back()) == l_true); } for (auto& kv : m_guards) { - expr * g = & kv.get_key(); - c.push_back(~ ctx.get_literal(g)); + c.push_back(~ mk_literal(&kv.get_key())); } TRACEFN("max-depth limit: add clause " << pp_lits(ctx, c)); SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict @@ -197,12 +195,12 @@ namespace smt { if (u().is_case_pred(a)) { TRACEFN("assign_case_pred_true "<< mk_pp(e,m())); // add to set of local assumptions, for depth-limit purpose - { - m_guards.insert(e, empty()); - m().inc_ref(e); - insert_ref_map trail_elt(m(), m_guards, e); - m_trail.push(trail_elt); - } + SASSERT(!m_guards.contains(e)); + m_guards.insert(e, empty()); + m().inc_ref(e); + insert_ref_map trail_elt(m(), m_guards, e); + m_trail.push(trail_elt); + if (m_guards.size() > get_max_depth()) { // too many body-expansions: depth-limit conflict max_depth_conflict(); @@ -233,27 +231,37 @@ namespace smt { return app_ref(u().mk_case_pred(p, args), m()); } + literal theory_recfun::mk_literal(expr* e) { + ctx().internalize(e, false); + literal lit = ctx().get_literal(e); + ctx().mark_as_relevant(lit); + return lit; + } + + literal theory_recfun::mk_eq_lit(expr* l, expr* r) { + literal lit = mk_eq(l, r, false); + ctx().mark_as_relevant(lit); + return lit; + } + void theory_recfun::assert_macro_axiom(case_expansion & e) { TRACEFN("assert_macro_axiom " << pp_case_expansion(e, m())); SASSERT(e.m_def->is_fun_macro()); - context & ctx = get_context(); auto & vars = e.m_def->get_vars(); expr_ref lhs(e.m_lhs, m()); // substitute `e.args` into the macro RHS expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m()); TRACEFN("macro expansion yields" << mk_pp(rhs,m())); // add unit clause `lhs = rhs` - literal l(mk_eq(lhs, rhs, true)); - ctx.mark_as_relevant(l); - TRACEFN("assert_macro_axiom: " << pp_lit(ctx, l)); - ctx.mk_th_axiom(get_id(), 1, &l); + literal lit = mk_eq_lit(lhs, rhs); + TRACEFN("assert_macro_axiom: " << pp_lit(ctx(), lit)); + ctx().mk_th_axiom(get_id(), 1, &lit); } void theory_recfun::assert_case_axioms(case_expansion & e) { TRACEFN("assert_case_axioms "<< pp_case_expansion(e,m()) << " with " << e.m_def->get_cases().size() << " cases"); SASSERT(e.m_def->is_fun_defined()); - context & ctx = get_context(); // add case-axioms for all case-paths auto & vars = e.m_def->get_vars(); for (recfun::case_def const & c : e.m_def->get_cases()) { @@ -261,24 +269,20 @@ namespace smt { literal_vector guards; for (auto & g : c.get_guards()) { expr_ref guard = apply_args(vars, e.m_args, g); - ctx.internalize(guard, false); - guards.push_back(~ctx.get_literal(guard)); + guards.push_back(~mk_literal(guard)); } app_ref pred_applied = apply_pred(c.get_pred(), e.m_args); SASSERT(u().owns_app(pred_applied)); - ctx.internalize(pred_applied, false); - literal concl = ctx.get_literal(pred_applied); - ctx.mark_as_relevant(concl); + literal concl = mk_literal(pred_applied); // assert `p(args) <=> And(guards)` (with CNF on the fly) for (literal g : guards) { literal c[2] = {~ concl, ~g}; - ctx.mark_as_relevant(g); - get_context().mk_th_axiom(get_id(), 2, c); + ctx().mk_th_axiom(get_id(), 2, c); } guards.push_back(concl); - get_context().mk_th_axiom(get_id(), guards.size(), guards.c_ptr()); + ctx().mk_th_axiom(get_id(), guards.size(), guards.c_ptr()); // also body-expand paths that do not depend on any defined fun if (c.is_immediate()) { @@ -290,7 +294,6 @@ namespace smt { void theory_recfun::assert_body_axiom(body_expansion & e) { TRACEFN("assert_body_axioms "<< pp_body_expansion(e,m())); - context & ctx = get_context(); recfun::def & d = *e.m_cdef->get_def(); auto & vars = d.get_vars(); auto & args = e.m_args; @@ -307,16 +310,11 @@ namespace smt { literal_vector clause; for (auto & g : e.m_cdef->get_guards()) { expr_ref guard = apply_args(vars, args, g); - ctx.internalize(guard, false); - literal l = ~ ctx.get_literal(guard); - ctx.mark_as_relevant(l); - clause.push_back(l); + clause.push_back(~mk_literal(guard)); } - literal l(mk_eq(lhs, rhs, true)); - ctx.mark_as_relevant(l); - clause.push_back(l); - TRACEFN("assert_body_axiom " << pp_lits(ctx, clause)); - ctx.mk_th_axiom(get_id(), clause.size(), clause.c_ptr()); + clause.push_back(mk_eq_lit(lhs, rhs)); + TRACEFN("assert_body_axiom " << pp_lits(ctx(), clause)); + ctx().mk_th_axiom(get_id(), clause.size(), clause.c_ptr()); } final_check_status theory_recfun::final_check_eh() { diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 68833be63..275d2ef59 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -121,6 +121,8 @@ namespace smt { void assert_case_axioms(case_expansion & e); void assert_body_axiom(body_expansion & e); void max_depth_conflict(void); + literal mk_literal(expr* e); + literal mk_eq_lit(expr* l, expr* r); protected: void push_case_expand(case_expansion&& e) { m_q_case_expand.push_back(e); } void push_body_expand(body_expansion&& e) { m_q_body_expand.push_back(e); } From bd53fa801edbe6166e95c0fda60f499c513e44a7 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 17 Oct 2018 21:42:18 -0700 Subject: [PATCH 41/65] handle case input format Signed-off-by: Nikolaj Bjorner --- src/cmd_context/extra_cmds/dbg_cmds.cpp | 6 +++ src/parsers/smt2/smt2parser.cpp | 64 ++++++++++++++++++------- src/smt/smt_context.cpp | 4 +- src/smt/smt_context.h | 6 +-- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/cmd_context/extra_cmds/dbg_cmds.cpp b/src/cmd_context/extra_cmds/dbg_cmds.cpp index 235576735..bdd2c8b97 100644 --- a/src/cmd_context/extra_cmds/dbg_cmds.cpp +++ b/src/cmd_context/extra_cmds/dbg_cmds.cpp @@ -91,6 +91,11 @@ UNARY_CMD(pp_shared_cmd, "dbg-pp-shared", "", "display shared subterms of ctx.regular_stream() << ")" << std::endl; }); +UNARY_CMD(assert_not_cmd, "assert-not", "", "assert negation", CPK_EXPR, expr *, { + expr_ref ne(ctx.m().mk_not(arg), ctx.m()); + ctx.assert_expr(ne); +}); + UNARY_CMD(num_shared_cmd, "dbg-num-shared", "", "return the number of shared subterms", CPK_EXPR, expr *, { shared_occs s(ctx.m()); s(arg); @@ -537,6 +542,7 @@ void install_dbg_cmds(cmd_context & ctx) { ctx.insert(alloc(shift_vars_cmd)); ctx.insert(alloc(pp_shared_cmd)); ctx.insert(alloc(num_shared_cmd)); + ctx.insert(alloc(assert_not_cmd)); ctx.insert(alloc(size_cmd)); ctx.insert(alloc(subst_cmd)); ctx.insert(alloc(bool_rewriter_cmd)); diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 8db6388ab..7458fc578 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -114,6 +114,7 @@ namespace smt2 { symbol m_define_fun_rec; symbol m_define_funs_rec; symbol m_match; + symbol m_case; symbol m_underscore; typedef std::pair named_expr; @@ -382,7 +383,9 @@ namespace smt2 { next(); return; } - throw parser_exception(msg); + std::ostringstream str; + str << msg << " got " << curr_id(); + throw parser_exception(str.str()); } symbol const & curr_id() const { return m_scanner.get_id(); } @@ -406,6 +409,7 @@ namespace smt2 { bool curr_id_is_underscore() const { SASSERT(curr_is_identifier()); return curr_id() == m_underscore; } bool curr_id_is_as() const { SASSERT(curr_is_identifier()); return curr_id() == m_as; } bool curr_id_is_match() const { SASSERT(curr_is_identifier()); return curr_id() == m_match; } + bool curr_id_is_case() const { return curr_id() == m_case; } bool curr_id_is_forall() const { SASSERT(curr_is_identifier()); return curr_id() == m_forall; } bool curr_id_is_exists() const { SASSERT(curr_is_identifier()); return curr_id() == m_exists; } bool curr_id_is_lambda() const { SASSERT(curr_is_identifier()); return curr_id() == m_lambda; } @@ -1319,7 +1323,13 @@ namespace smt2 { /** * SMT-LIB 2.6 pattern matches are of the form - * (match t ((p1 t1) ... (pm+1 tm+1))) + * + * (match t ((p1 t1) ... (pm+1 tm+1))) + * + * precursor form is + * + * (match t (case p1 t1) (case p2 t2) ... ) + * */ void push_match_frame() { SASSERT(curr_is_identifier()); @@ -1336,21 +1346,42 @@ namespace smt2 { sort* srt = m().get_sort(t); check_lparen_next("pattern bindings should be enclosed in a parenthesis"); - while (!curr_is_rparen()) { - m_env.begin_scope(); - unsigned num_bindings = m_num_bindings; - check_lparen_next("invalid pattern binding, '(' expected"); - parse_match_pattern(srt); - patterns.push_back(expr_stack().back()); - expr_stack().pop_back(); - parse_expr(); - cases.push_back(expr_stack().back()); - expr_stack().pop_back(); - m_num_bindings = num_bindings; - m_env.end_scope(); - check_rparen_next("invalid pattern binding, ')' expected"); + if (curr_id_is_case()) { + while (curr_id_is_case()) { + next(); + m_env.begin_scope(); + unsigned num_bindings = m_num_bindings; + parse_match_pattern(srt); + patterns.push_back(expr_stack().back()); + expr_stack().pop_back(); + parse_expr(); + cases.push_back(expr_stack().back()); + expr_stack().pop_back(); + m_num_bindings = num_bindings; + m_env.end_scope(); + check_rparen_next("invalid pattern binding, ')' expected"); + if (curr_is_lparen()) { + next(); + } + } + } + else { + while (!curr_is_rparen()) { + m_env.begin_scope(); + unsigned num_bindings = m_num_bindings; + parse_match_pattern(srt); + patterns.push_back(expr_stack().back()); + expr_stack().pop_back(); + check_lparen_next("invalid pattern binding, '(' expected"); + parse_expr(); + cases.push_back(expr_stack().back()); + expr_stack().pop_back(); + m_num_bindings = num_bindings; + m_env.end_scope(); + check_rparen_next("invalid pattern binding, ')' expected"); + } + next(); } - next(); m_num_expr_frames = num_frames + 1; expr_stack().push_back(compile_patterns(t, patterns, cases)); } @@ -3013,6 +3044,7 @@ namespace smt2 { m_define_fun_rec("define-fun-rec"), m_define_funs_rec("define-funs-rec"), m_match("match"), + m_case("case"), m_underscore("_"), m_num_open_paren(0), m_current_file(filename) { diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index f275b0ebf..f7b7bdbea 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3207,7 +3207,7 @@ namespace smt { } else { set_conflict(b_justification(tmp_clause.first), null_literal); - } + } VERIFY(!resolve_conflict()); return l_false; next_clause: @@ -3770,7 +3770,7 @@ namespace smt { } m_stats.m_num_final_checks++; - TRACE("final_check_stats", tout << "m_stats.m_num_final_checks = " << m_stats.m_num_final_checks << "\n";); + TRACE("final_check_stats", tout << "m_stats.m_num_final_checks = " << m_stats.m_num_final_checks << "\n";); final_check_status ok = m_qmanager->final_check_eh(false); if (ok != FC_DONE) diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index a349880e3..c3e9c3ffc 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1010,9 +1010,9 @@ namespace smt { void restore_theory_vars(enode * r2, enode * r1); void push_eq(enode * lhs, enode * rhs, eq_justification const & js) { - SASSERT(lhs != rhs); - SASSERT(lhs->get_root() != rhs->get_root()); - m_eq_propagation_queue.push_back(new_eq(lhs, rhs, js)); + if (lhs->get_root() != rhs->get_root()) { + m_eq_propagation_queue.push_back(new_eq(lhs, rhs, js)); + } } void push_new_congruence(enode * n1, enode * n2, bool used_commutativity) { From 2f5f5469901f2bc3c77a414a14c0ee7b67afcaf8 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 18 Oct 2018 09:02:20 -0700 Subject: [PATCH 42/65] ctx Signed-off-by: Nikolaj Bjorner --- src/smt/theory_recfun.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 23d9e7f2c..32c2fe8c5 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -106,7 +106,7 @@ namespace smt { * body-expand it. */ void theory_recfun::relevant_eh(app * n) { - SASSERT(get_context().relevancy()); + SASSERT(ctx().relevancy()); if (u().is_defined(n)) { TRACEFN("relevant_eh: (defined) " << mk_pp(n, m())); case_expansion e(u(), n); @@ -140,11 +140,10 @@ namespace smt { } void theory_recfun::propagate() { - context & ctx = get_context(); for (literal_vector & c : m_q_clauses) { - TRACEFN("add axiom " << pp_lits(ctx, c)); - ctx.mk_th_axiom(get_id(), c.size(), c.c_ptr()); + TRACEFN("add axiom " << pp_lits(ctx(), c)); + ctx().mk_th_axiom(get_id(), c.size(), c.c_ptr()); } m_q_clauses.clear(); @@ -169,20 +168,19 @@ namespace smt { void theory_recfun::max_depth_conflict() { TRACEFN("max-depth conflict"); - context & ctx = get_context(); literal_vector c; // make clause `depth_limit => V_{g : guards} ~ g` { // first literal must be the depth limit one app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); c.push_back(~mk_literal(dlimit)); - SASSERT(ctx.get_assignment(c.back()) == l_true); + SASSERT(ctx().get_assignment(c.back()) == l_true); } for (auto& kv : m_guards) { - c.push_back(~ mk_literal(&kv.get_key())); + c.push_back(~ mk_literal(kv.m_key)); } - TRACEFN("max-depth limit: add clause " << pp_lits(ctx, c)); - SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx.get_assignment(l) == l_false; })); // conflict + TRACEFN("max-depth limit: add clause " << pp_lits(ctx(), c)); + SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx().get_assignment(l) == l_false; })); // conflict m_q_clauses.push_back(std::move(c)); } @@ -222,7 +220,7 @@ namespace smt { var_subst subst(m(), true); expr_ref new_body(m()); new_body = subst(e, args.size(), args.c_ptr()); - get_context().get_rewriter()(new_body); // simplify + ctx().get_rewriter()(new_body); // simplify return new_body; } From d22a0d04ed78e874cfa87ad295b0d1b826b9c231 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 18 Oct 2018 10:01:32 -0700 Subject: [PATCH 43/65] n/a Signed-off-by: Nikolaj Bjorner --- src/ast/ast.h | 4 + src/ast/recfun_decl_plugin.cpp | 88 +++++++++++---------- src/ast/recfun_decl_plugin.h | 21 +++-- src/smt/smt_context.h | 4 + src/smt/theory_recfun.cpp | 135 ++++++++++++++++++--------------- src/smt/theory_recfun.h | 18 ++--- 6 files changed, 144 insertions(+), 126 deletions(-) diff --git a/src/ast/ast.h b/src/ast/ast.h index c1193dfbd..acef0c659 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -707,6 +707,10 @@ public: func_decl * get_decl() const { return m_decl; } family_id get_family_id() const { return get_decl()->get_family_id(); } decl_kind get_decl_kind() const { return get_decl()->get_decl_kind(); } + symbol const& get_name() const { return get_decl()->get_name(); } + unsigned get_num_parameters() const { return get_decl()->get_num_parameters(); } + parameter const& get_parameter(unsigned idx) const { return get_decl()->get_parameter(idx); } + parameter const* get_parameters() const { return get_decl()->get_parameters(); } bool is_app_of(family_id fid, decl_kind k) const { return get_family_id() == fid && get_decl_kind() == k; } unsigned get_num_args() const { return m_num_args; } expr * get_arg(unsigned idx) const { SASSERT(idx < m_num_args); return m_args[idx]; } diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index f4b237f9a..de8bf94b1 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -23,7 +23,7 @@ Revision History: #include "ast/ast_pp.h" #include "util/scoped_ptr_vector.h" -#define DEBUG(x) TRACE("recfun", tout << x << '\n';) +#define TRACEFN(x) TRACE("recfun", tout << x << '\n';) #define VALIDATE_PARAM(m, _pred_) if (!(_pred_)) m.raise_exception("invalid parameter to recfun " #_pred_); namespace recfun { @@ -42,18 +42,18 @@ namespace recfun { sort_ref_vector const & arg_sorts, unsigned num_guards, expr ** guards, expr* rhs) : m_pred(m, fid, name, arg_sorts), m_guards(m), m_rhs(expr_ref(rhs,m)), m_def(d) { - for (unsigned i=0; inext) { app * ite = choices->ite; - SASSERT(m.is_ite(ite)); + expr* c = nullptr, *th = nullptr, *el = nullptr; + VERIFY(m.is_ite(ite, c, th, el)); // condition to add to the guard - expr * cond0 = ite->get_arg(0); - conditions.push_back(choices->sign ? cond0 : m.mk_not(cond0)); + conditions.push_back(choices->sign ? c : m.mk_not(c)); // binding to add to the substitution - subst.insert(ite, choices->sign ? ite->get_arg(1) : ite->get_arg(2)); + subst.insert(ite, choices->sign ? th : el); } } @@ -183,11 +185,11 @@ namespace recfun { } void def::add_case(std::string & name, unsigned n_conditions, expr ** conditions, expr * rhs, bool is_imm) { - case_def c(m(), m_fid, this, name, get_domain(), n_conditions, conditions, rhs); + case_def c(m, m_fid, this, name, get_domain(), n_conditions, conditions, rhs); c.set_is_immediate(is_imm); - DEBUG("add_case " << name << " " << mk_pp(rhs, m()) + TRACEFN("add_case " << name << " " << mk_pp(rhs, m) << " :is_imm " << is_imm - << " :guards " << mk_pp_vec(n_conditions, (ast**)conditions, m())); + << " :guards " << mk_pp_vec(n_conditions, (ast**)conditions, m)); m_cases.push_back(c); } @@ -197,12 +199,12 @@ namespace recfun { unsigned n_vars, var *const * vars, expr* rhs0) { if (m_cases.size() != 0) { - DEBUG("bug: cases for " << m_name << " has cases already"); + TRACEFN("bug: " << m_name << " has cases already"); UNREACHABLE(); } SASSERT(n_vars = m_domain.size()); - DEBUG("compute cases " << mk_pp(rhs0, m())); + TRACEFN("compute cases " << mk_pp(rhs0, m)); unsigned case_idx = 0; std::string name; @@ -211,18 +213,18 @@ namespace recfun { name.append(m_name.bare_str()); name.append("_"); - for (unsigned i=0; iget_num_args(); ++i) - stack.push_back(a->get_arg(i)); + for (expr * arg : *to_app(e)) { + stack.push_back(arg); + } } } } @@ -280,7 +282,8 @@ namespace recfun { if (b.to_split != nullptr) { // split one `ite`, which will lead to distinct (sets of) cases app * ite = b.to_split->ite; - SASSERT(m().is_ite(ite)); + expr* c = nullptr, *th = nullptr, *el = nullptr; + VERIFY(m.is_ite(ite, c, th, el)); /* explore both positive choice and negative choice. * each contains a longer path, with `ite` mapping to `true` (resp. `false), @@ -291,10 +294,11 @@ namespace recfun { branch b_pos(st.cons_choice(ite, true, b.path), b.to_split->next, - st.cons_unfold(ite->get_arg(0), ite->get_arg(1), b.to_unfold)); + st.cons_unfold(c, th, b.to_unfold)); + branch b_neg(st.cons_choice(ite, false, b.path), b.to_split->next, - st.cons_unfold(ite->get_arg(0), ite->get_arg(2), b.to_unfold)); + st.cons_unfold(c, el, b.to_unfold)); st.push_branch(b_neg); st.push_branch(b_pos); @@ -302,20 +306,20 @@ namespace recfun { else { // leaf of the search tree - expr_ref_vector conditions_raw(m()); - expr_substitution subst(m()); - convert_path(m(), b.path, conditions_raw, subst); + expr_ref_vector conditions_raw(m); + expr_substitution subst(m); + convert_path(m, b.path, conditions_raw, subst); // substitute, to get rid of `ite` terms - expr_ref case_rhs = replace_subst(th_rw, m(), subst, rhs); - expr_ref_vector conditions(m()); + expr_ref case_rhs = replace_subst(th_rw, m, subst, rhs); + expr_ref_vector conditions(m); for (expr * g : conditions_raw) { - expr_ref g_subst(replace_subst(th_rw, m(), subst, g), m()); + expr_ref g_subst(replace_subst(th_rw, m, subst, g), m); conditions.push_back(g_subst); } - unsigned old_name_len = name.size(); + size_t old_name_len = name.size(); { // TODO: optimize? this does many copies std::ostringstream sout; sout << ((unsigned long) case_idx); @@ -330,7 +334,7 @@ namespace recfun { } } - DEBUG("done analysing " << get_name()); + TRACEFN("done analysing " << get_name()); } /* @@ -495,4 +499,4 @@ namespace recfun { } } } -} \ No newline at end of file +} diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index b51717c1d..6c8824a6a 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -21,10 +21,10 @@ Revision History: #include "ast/rewriter/th_rewriter.h" namespace recfun { - class case_def; // cases; - ast_manager & m_manager; + ast_manager & m; symbol m_name; //get_family_id() == m_family_id; } //(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), m_trail(*this), @@ -42,8 +43,8 @@ namespace smt { theory_recfun::~theory_recfun() { reset_queues(); - for (auto & kv : m_guards) { - m().dec_ref(kv.m_key); + for (expr* g : m_guards) { + m.dec_ref(g); } m_guards.reset(); } @@ -61,28 +62,26 @@ namespace smt { } bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { - context & ctx = get_context(); for (expr * arg : *atom) { - ctx.internalize(arg, false); + ctx().internalize(arg, false); } - if (!ctx.e_internalized(atom)) { - ctx.mk_enode(atom, false, true, false); + if (!ctx().e_internalized(atom)) { + ctx().mk_enode(atom, false, true, false); } - if (!ctx.b_internalized(atom)) { - bool_var v = ctx.mk_bool_var(atom); - ctx.set_var_theory(v, get_id()); + if (!ctx().b_internalized(atom)) { + bool_var v = ctx().mk_bool_var(atom); + ctx().set_var_theory(v, get_id()); } return true; } bool theory_recfun::internalize_term(app * term) { - context & ctx = get_context(); for (expr* e : *term) { - ctx.internalize(e, false); + ctx().internalize(e, false); } // the internalization of the arguments may have triggered the internalization of term. - if (!ctx.e_internalized(term)) { - ctx.mk_enode(term, false, false, true); + if (!ctx().e_internalized(term)) { + ctx().mk_enode(term, false, false, true); } return true; } @@ -108,7 +107,7 @@ namespace smt { void theory_recfun::relevant_eh(app * n) { SASSERT(ctx().relevancy()); if (u().is_defined(n)) { - TRACEFN("relevant_eh: (defined) " << mk_pp(n, m())); + TRACEFN("relevant_eh: (defined) " << mk_pp(n, m)); case_expansion e(u(), n); push_case_expand(std::move(e)); } @@ -143,7 +142,7 @@ namespace smt { for (literal_vector & c : m_q_clauses) { TRACEFN("add axiom " << pp_lits(ctx(), c)); - ctx().mk_th_axiom(get_id(), c.size(), c.c_ptr()); + ctx().mk_th_axiom(get_id(), c); } m_q_clauses.clear(); @@ -176,8 +175,8 @@ namespace smt { c.push_back(~mk_literal(dlimit)); SASSERT(ctx().get_assignment(c.back()) == l_true); } - for (auto& kv : m_guards) { - c.push_back(~ mk_literal(kv.m_key)); + for (expr * g : m_guards) { + c.push_back(~ mk_literal(g)); } TRACEFN("max-depth limit: add clause " << pp_lits(ctx(), c)); SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx().get_assignment(l) == l_false; })); // conflict @@ -185,18 +184,21 @@ namespace smt { m_q_clauses.push_back(std::move(c)); } - // if `is_true` and `v = C_f_i(t1...tn)`, then body-expand i-th case of `f(t1...tn)` + /** + * if `is_true` and `v = C_f_i(t1...tn)`, + * then body-expand i-th case of `f(t1...tn)` + */ void theory_recfun::assign_eh(bool_var v, bool is_true) { - expr* e = get_context().bool_var2expr(v); + expr* e = ctx().bool_var2expr(v); if (!is_true || !is_app(e)) return; app* a = to_app(e); if (u().is_case_pred(a)) { - TRACEFN("assign_case_pred_true "<< mk_pp(e,m())); + TRACEFN("assign_case_pred_true "<< mk_pp(e,m)); // add to set of local assumptions, for depth-limit purpose SASSERT(!m_guards.contains(e)); - m_guards.insert(e, empty()); - m().inc_ref(e); - insert_ref_map trail_elt(m(), m_guards, e); + m_guards.insert(e); + m.inc_ref(e); + insert_ref_map trail_elt(m, m_guards, e); m_trail.push(trail_elt); if (m_guards.size() > get_max_depth()) { @@ -215,18 +217,17 @@ namespace smt { expr_ref theory_recfun::apply_args(recfun::vars const & vars, ptr_vector const & args, expr * e) { - // check that var order is standard - SASSERT(vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0); - var_subst subst(m(), true); - expr_ref new_body(m()); + SASSERT(is_standard_order(vars)); + var_subst subst(m, true); + expr_ref new_body(m); new_body = subst(e, args.size(), args.c_ptr()); ctx().get_rewriter()(new_body); // simplify return new_body; } app_ref theory_recfun::apply_pred(recfun::case_pred const & p, - ptr_vector const & args) { - return app_ref(u().mk_case_pred(p, args), m()); + ptr_vector const & args) { + return app_ref(u().mk_case_pred(p, args), m); } literal theory_recfun::mk_literal(expr* e) { @@ -242,47 +243,54 @@ namespace smt { return lit; } + /** + * For functions f(args) that are given as macros f(vs) = rhs + * + * 1. substitute `e.args` for `vs` into the macro rhs + * 2. add unit clause `f(args) = rhs` + */ void theory_recfun::assert_macro_axiom(case_expansion & e) { - TRACEFN("assert_macro_axiom " << pp_case_expansion(e, m())); SASSERT(e.m_def->is_fun_macro()); auto & vars = e.m_def->get_vars(); - expr_ref lhs(e.m_lhs, m()); - // substitute `e.args` into the macro RHS - expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m()); - TRACEFN("macro expansion yields" << mk_pp(rhs,m())); - // add unit clause `lhs = rhs` + expr_ref lhs(e.m_lhs, m); + expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m); literal lit = mk_eq_lit(lhs, rhs); - TRACEFN("assert_macro_axiom: " << pp_lit(ctx(), lit)); ctx().mk_th_axiom(get_id(), 1, &lit); + TRACEFN("case expansion " << pp_case_expansion(e, m) << "\n" << + "macro expansion yields " << mk_pp(rhs,m) << "\n" << + "literal " << pp_lit(ctx(), lit)); } + /** + * Add case axioms for every case expansion path. + * + * assert `p(args) <=> And(guards)` (with CNF on the fly) + * + * also body-expand paths that do not depend on any defined fun + */ void theory_recfun::assert_case_axioms(case_expansion & e) { - TRACEFN("assert_case_axioms "<< pp_case_expansion(e,m()) + TRACEFN("assert_case_axioms "<< pp_case_expansion(e,m) << " with " << e.m_def->get_cases().size() << " cases"); SASSERT(e.m_def->is_fun_defined()); // add case-axioms for all case-paths auto & vars = e.m_def->get_vars(); for (recfun::case_def const & c : e.m_def->get_cases()) { // applied predicate to `args` - literal_vector guards; - for (auto & g : c.get_guards()) { - expr_ref guard = apply_args(vars, e.m_args, g); - guards.push_back(~mk_literal(guard)); - } app_ref pred_applied = apply_pred(c.get_pred(), e.m_args); SASSERT(u().owns_app(pred_applied)); literal concl = mk_literal(pred_applied); - // assert `p(args) <=> And(guards)` (with CNF on the fly) - - for (literal g : guards) { - literal c[2] = {~ concl, ~g}; + literal_vector guards; + guards.push_back(concl); + for (auto & g : c.get_guards()) { + expr_ref ga = apply_args(vars, e.m_args, g); + literal guard = mk_literal(ga); + guards.push_back(~guard); + literal c[2] = {~concl, guard}; ctx().mk_th_axiom(get_id(), 2, c); } - guards.push_back(concl); - ctx().mk_th_axiom(get_id(), guards.size(), guards.c_ptr()); + ctx().mk_th_axiom(get_id(), guards); - // also body-expand paths that do not depend on any defined fun if (c.is_immediate()) { body_expansion be(c, e.m_args); assert_body_axiom(be); @@ -290,20 +298,21 @@ namespace smt { } } + /** + * For a guarded definition guards => f(vars) = rhs + * and occurrence f(args) + * + * substitute `args` for `vars` in guards, and rhs + * add axiom guards[args/vars] => f(args) = rhs[args/vars] + * + */ void theory_recfun::assert_body_axiom(body_expansion & e) { - TRACEFN("assert_body_axioms "<< pp_body_expansion(e,m())); recfun::def & d = *e.m_cdef->get_def(); auto & vars = d.get_vars(); auto & args = e.m_args; - // check that var order is standard - SASSERT(vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0); - expr_ref lhs(u().mk_fun_defined(d, args), m()); - // substitute `e.args` into the RHS of this particular case + SASSERT(is_standard_order(vars)); + expr_ref lhs(u().mk_fun_defined(d, args), m); expr_ref rhs = apply_args(vars, args, e.m_cdef->get_rhs()); - // substitute `e.args` into the guard of this particular case, to make - // the `condition` part of the clause `conds => lhs=rhs` - - // now build the axiom `conds => lhs = rhs` literal_vector clause; for (auto & g : e.m_cdef->get_guards()) { @@ -311,8 +320,9 @@ namespace smt { clause.push_back(~mk_literal(guard)); } clause.push_back(mk_eq_lit(lhs, rhs)); - TRACEFN("assert_body_axiom " << pp_lits(ctx(), clause)); - ctx().mk_th_axiom(get_id(), clause.size(), clause.c_ptr()); + ctx().mk_th_axiom(get_id(), clause); + TRACEFN("body " << pp_body_expansion(e,m)); + TRACEFN("clause " << pp_lits(ctx(), clause)); } final_check_status theory_recfun::final_check_eh() { @@ -321,15 +331,14 @@ namespace smt { void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); - TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m())); + TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m)); assumptions.push_back(dlimit); } - // if `dlimit` occurs in unsat core, return "unknown" lbool theory_recfun::validate_unsat_core(expr_ref_vector & unsat_core) { for (auto & e : unsat_core) { - if (is_app(e) && m_util.is_depth_limit(to_app(e))) + if (u().is_depth_limit(e)) return l_undef; } return l_false; diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 275d2ef59..019ef7918 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -33,7 +33,7 @@ namespace smt { stats() { reset(); } }; - // one case-expansion of `f(t1…tn)` + // one case-expansion of `f(t1...tn)` struct case_expansion { expr * m_lhs; // the term to expand recfun_def * m_def; @@ -64,18 +64,16 @@ namespace smt { friend std::ostream& operator<<(std::ostream&, pp_case_expansion const &); - // one body-expansion of `f(t1…tn)` using a `C_f_i(t1…tn)` + // one body-expansion of `f(t1...tn)` using a `C_f_i(t1...tn)` struct body_expansion { recfun_case_def const * m_cdef; ptr_vector m_args; body_expansion(recfun_util& u, app * n) : m_cdef(0), m_args() { SASSERT(u.is_case_pred(n)); - func_decl * d = n->get_decl(); - const symbol& name = d->get_name(); - m_cdef = &u.get_case_def(name); - for (unsigned i = 0; i < n->get_num_args(); ++i) - m_args.push_back(n->get_arg(i)); + m_cdef = &u.get_case_def(n->get_name()); + for (expr * arg : *n) + m_args.push_back(arg); } body_expansion(recfun_case_def const & d, ptr_vector & args) : m_cdef(&d), m_args(args) {} body_expansion(body_expansion const & from): m_cdef(from.m_cdef), m_args(from.m_args) {} @@ -90,11 +88,11 @@ namespace smt { friend std::ostream& operator<<(std::ostream&, pp_body_expansion const &); - struct empty{}; typedef trail_stack th_trail_stack; - typedef obj_map guard_set; + typedef obj_hashtable guard_set; + ast_manager& m; recfun_decl_plugin& m_plugin; recfun_util& m_util; stats m_stats; @@ -107,7 +105,6 @@ namespace smt { vector m_q_clauses; recfun_util & u() const { return m_util; } - ast_manager & m() { return get_manager(); } bool is_defined(app * f) const { return u().is_defined(f); } bool is_case_pred(app * f) const { return u().is_case_pred(f); } @@ -123,6 +120,7 @@ namespace smt { void max_depth_conflict(void); literal mk_literal(expr* e); literal mk_eq_lit(expr* l, expr* r); + bool is_standard_order(recfun::vars const& vars) const { return vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0; } protected: void push_case_expand(case_expansion&& e) { m_q_case_expand.push_back(e); } void push_body_expand(body_expansion&& e) { m_q_body_expand.push_back(e); } From 35eb6eccd1daaacbcda760a43f25f37e0a3eda56 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 18 Oct 2018 17:14:10 -0700 Subject: [PATCH 44/65] iterative deepening Signed-off-by: Nikolaj Bjorner --- src/ast/recfun_decl_plugin.cpp | 7 +-- src/smt/smt_context.cpp | 34 +++++++++++--- src/smt/smt_context.h | 4 +- src/smt/smt_theory.h | 9 ++++ src/smt/theory_recfun.cpp | 85 +++++++++++++++++++++------------- src/smt/theory_recfun.h | 15 ++---- 6 files changed, 101 insertions(+), 53 deletions(-) diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index de8bf94b1..1cd9a4fb8 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -184,7 +184,8 @@ namespace recfun { return res; } - void def::add_case(std::string & name, unsigned n_conditions, expr ** conditions, expr * rhs, bool is_imm) { + void def::add_case(std::string & name, unsigned n_conditions, + expr ** conditions, expr * rhs, bool is_imm) { case_def c(m, m_fid, this, name, get_domain(), n_conditions, conditions, rhs); c.set_is_immediate(is_imm); TRACEFN("add_case " << name << " " << mk_pp(rhs, m) @@ -202,7 +203,7 @@ namespace recfun { TRACEFN("bug: " << m_name << " has cases already"); UNREACHABLE(); } - SASSERT(n_vars = m_domain.size()); + SASSERT(n_vars == m_domain.size()); TRACEFN("compute cases " << mk_pp(rhs0, m)); @@ -235,7 +236,7 @@ namespace recfun { if (m_macro) { // constant function or trivial control flow, only one (dummy) case name.append("dummy"); - add_case(name, 0, 0, rhs); + add_case(name, 0, nullptr, rhs); return; } diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index f7b7bdbea..4b8b1b9d9 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3266,6 +3266,18 @@ namespace smt { m_assumptions.reset(); } + bool context::should_research(lbool r) { + if (r != l_false || m_unsat_core.empty()) { + return false; + } + for (theory* th : m_theory_set) { + if (th->should_research(m_unsat_core)) { + return true; + } + } + return false; + } + lbool context::mk_unsat_core(lbool r) { if (r != l_false) return r; SASSERT(inconsistent()); @@ -3353,7 +3365,7 @@ namespace smt { add_theory_assumptions(theory_assumptions); if (!theory_assumptions.empty()) { TRACE("search", tout << "Adding theory assumptions to context" << std::endl;); - return check(theory_assumptions.size(), theory_assumptions.c_ptr(), reset_cancel, true); + return check(0, nullptr, reset_cancel); } internalize_assertions(); @@ -3407,19 +3419,23 @@ namespace smt { } } - lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel, bool already_did_theory_assumptions) { + lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel) { if (!check_preamble(reset_cancel)) return l_undef; SASSERT(at_base_level()); setup_context(false); expr_ref_vector asms(m_manager, num_assumptions, assumptions); - if (!already_did_theory_assumptions) add_theory_assumptions(asms); + add_theory_assumptions(asms); // introducing proxies: if (!validate_assumptions(asms)) return l_undef; TRACE("unsat_core_bug", tout << asms << "\n";); internalize_assertions(); init_assumptions(asms); TRACE("before_search", display(tout);); - lbool r = search(); - r = mk_unsat_core(r); + lbool r; + do { + r = search(); + r = mk_unsat_core(r); + } + while (should_research(r)); r = check_finalize(r); return r; } @@ -3435,8 +3451,12 @@ namespace smt { internalize_assertions(); init_assumptions(asms); for (auto const& clause : clauses) init_clause(clause); - lbool r = search(); - r = mk_unsat_core(r); + lbool r; + do { + r = search(); + r = mk_unsat_core(r); + } + while (should_research(r)); r = check_finalize(r); return r; } diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 5902f9b2c..45341d368 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1137,6 +1137,8 @@ namespace smt { void add_theory_assumptions(expr_ref_vector & theory_assumptions); lbool mk_unsat_core(lbool result); + + bool should_research(lbool result); void validate_unsat_core(); @@ -1524,7 +1526,7 @@ namespace smt { void pop(unsigned num_scopes); - lbool check(unsigned num_assumptions = 0, expr * const * assumptions = nullptr, bool reset_cancel = true, bool already_did_theory_assumptions = false); + lbool check(unsigned num_assumptions = 0, expr * const * assumptions = nullptr, bool reset_cancel = true); lbool check(expr_ref_vector const& cube, vector const& clauses); diff --git a/src/smt/smt_theory.h b/src/smt/smt_theory.h index fa0ec0c82..15b0aeed6 100644 --- a/src/smt/smt_theory.h +++ b/src/smt/smt_theory.h @@ -194,6 +194,15 @@ namespace smt { return l_false; } + /** + \brief This method is called from the smt_context when an unsat core is generated. + The theory may tell the solver to perform iterative deepening by invalidating + this unsat core and increasing some resource constraints. + */ + virtual bool should_research(expr_ref_vector & unsat_core) { + return false; + } + /** \brief This method is invoked before the search starts. */ diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 220cfcbbf..6f62cccc0 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -32,12 +32,10 @@ namespace smt { m(m), m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), - m_trail(*this), - m_guards(), + m_guards(m), m_max_depth(0), m_q_case_expand(), - m_q_body_expand(), - m_q_clauses() + m_q_body_expand() { } @@ -89,11 +87,9 @@ namespace smt { void theory_recfun::reset_queues() { m_q_case_expand.reset(); m_q_body_expand.reset(); - m_q_clauses.reset(); } void theory_recfun::reset_eh() { - m_trail.reset(); reset_queues(); m_stats.reset(); theory::reset_eh(); @@ -116,12 +112,10 @@ namespace smt { void theory_recfun::push_scope_eh() { TRACEFN("push_scope"); theory::push_scope_eh(); - m_trail.push_scope(); } void theory_recfun::pop_scope_eh(unsigned num_scopes) { TRACEFN("pop_scope " << num_scopes); - m_trail.pop_scope(num_scopes); theory::pop_scope_eh(num_scopes); reset_queues(); } @@ -131,15 +125,15 @@ namespace smt { reset_queues(); theory::restart_eh(); } - + bool theory_recfun::can_propagate() { return ! (m_q_case_expand.empty() && m_q_body_expand.empty() && m_q_clauses.empty()); } - + void theory_recfun::propagate() { - + for (literal_vector & c : m_q_clauses) { TRACEFN("add axiom " << pp_lits(ctx(), c)); ctx().mk_th_axiom(get_id(), c); @@ -179,7 +173,7 @@ namespace smt { c.push_back(~ mk_literal(g)); } TRACEFN("max-depth limit: add clause " << pp_lits(ctx(), c)); - SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx().get_assignment(l) == l_false; })); // conflict + SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx().get_assignment(l) == l_false; })); // conflict m_q_clauses.push_back(std::move(c)); } @@ -190,26 +184,23 @@ namespace smt { */ void theory_recfun::assign_eh(bool_var v, bool is_true) { expr* e = ctx().bool_var2expr(v); - if (!is_true || !is_app(e)) return; - app* a = to_app(e); - if (u().is_case_pred(a)) { - TRACEFN("assign_case_pred_true "<< mk_pp(e,m)); + if (is_true && u().is_case_pred(e)) { + app* a = to_app(e); + TRACEFN("assign_case_pred_true " << mk_pp(e, m)); // add to set of local assumptions, for depth-limit purpose - SASSERT(!m_guards.contains(e)); - m_guards.insert(e); - m.inc_ref(e); - insert_ref_map trail_elt(m, m_guards, e); - m_trail.push(trail_elt); + SASSERT(!m_guards.contains(a)); + m_guards.push_back(a); + ctx().push_trail(push_back_vector(m_guards)); - if (m_guards.size() > get_max_depth()) { - // too many body-expansions: depth-limit conflict - max_depth_conflict(); - } - else { + if (m_guards.size() <= get_max_depth()) { // body-expand body_expansion b_e(u(), a); push_body_expand(std::move(b_e)); } + else { + // too many body-expansions: depth-limit conflict + max_depth_conflict(); + } } } @@ -238,7 +229,19 @@ namespace smt { } literal theory_recfun::mk_eq_lit(expr* l, expr* r) { - literal lit = mk_eq(l, r, false); + literal lit; + if (m.is_true(r) || m.is_false(r)) { + std::swap(l, r); + } + if (m.is_true(l)) { + lit = mk_literal(r); + } + else if (m.is_false(l)) { + lit = ~mk_literal(r); + } + else { + lit = mk_eq(l, r, false); + } ctx().mark_as_relevant(lit); return lit; } @@ -326,6 +329,21 @@ namespace smt { } final_check_status theory_recfun::final_check_eh() { + if (m_guards.size() > get_max_depth()) { +#if 1 + return FC_GIVEUP; +#else + for (unsigned i = get_max_depth(); i < m_guards.size(); ++i) { + app* a = m_guards.get(i); + body_expansion b_e(u(), a); + push_body_expand(std::move(b_e)); + } + unsigned new_depth = m_guards.size() + 1; + IF_VERBOSE(2, verbose_stream() << "(smt.recfun :new-depth " << new_depth << ")\n"); + set_max_depth(new_depth); + return FC_CONTINUE; +#endif + } return FC_DONE; } @@ -335,13 +353,16 @@ namespace smt { assumptions.push_back(dlimit); } - // if `dlimit` occurs in unsat core, return "unknown" - lbool theory_recfun::validate_unsat_core(expr_ref_vector & unsat_core) { + // if `dlimit` occurs in unsat core, return 'true' + bool theory_recfun::should_research(expr_ref_vector & unsat_core) { for (auto & e : unsat_core) { - if (u().is_depth_limit(e)) - return l_undef; + if (u().is_depth_limit(e)) { + unsigned new_depth = (3 * (1 + get_max_depth())) / 2; + set_max_depth(new_depth); + return true; + } } - return l_false; + return false; } void theory_recfun::display(std::ostream & out) const { diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 019ef7918..a2ec27314 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -72,8 +72,7 @@ namespace smt { body_expansion(recfun_util& u, app * n) : m_cdef(0), m_args() { SASSERT(u.is_case_pred(n)); m_cdef = &u.get_case_def(n->get_name()); - for (expr * arg : *n) - m_args.push_back(arg); + m_args.append(n->get_num_args(), n->get_args()); } body_expansion(recfun_case_def const & d, ptr_vector & args) : m_cdef(&d), m_args(args) {} body_expansion(body_expansion const & from): m_cdef(from.m_cdef), m_args(from.m_args) {} @@ -88,16 +87,11 @@ namespace smt { friend std::ostream& operator<<(std::ostream&, pp_body_expansion const &); - - typedef trail_stack th_trail_stack; - typedef obj_hashtable guard_set; - ast_manager& m; recfun_decl_plugin& m_plugin; recfun_util& m_util; stats m_stats; - th_trail_stack m_trail; - guard_set m_guards; // true case-preds + app_ref_vector m_guards; // true case-preds unsigned m_max_depth; // for fairness and termination vector m_q_case_expand; @@ -117,8 +111,8 @@ namespace smt { void assert_macro_axiom(case_expansion & e); void assert_case_axioms(case_expansion & e); void assert_body_axiom(body_expansion & e); - void max_depth_conflict(void); literal mk_literal(expr* e); + void max_depth_conflict(); literal mk_eq_lit(expr* l, expr* r); bool is_standard_order(recfun::vars const& vars) const { return vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0; } protected: @@ -137,7 +131,7 @@ namespace smt { void restart_eh() override; bool can_propagate() override; void propagate() override; - lbool validate_unsat_core(expr_ref_vector &) override; + bool should_research(expr_ref_vector &) override; void new_eq_eh(theory_var v1, theory_var v2) override {} void new_diseq_eh(theory_var v1, theory_var v2) override {} @@ -152,6 +146,7 @@ namespace smt { virtual void collect_statistics(::statistics & st) const override; unsigned get_max_depth() const { return m_max_depth; } void set_max_depth(unsigned n) { SASSERT(n>0); m_max_depth = n; } + void inc_max_depth() { ++m_max_depth; } }; } From c0556b2f643de03c47f0d3887349b73adb849be2 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 18 Oct 2018 17:53:11 -0700 Subject: [PATCH 45/65] iterative deepening per recursive function Signed-off-by: Nikolaj Bjorner --- src/ast/recfun_decl_plugin.h | 5 +++ src/smt/theory_recfun.cpp | 67 ++++++++++++++++++------------------ src/smt/theory_recfun.h | 25 +++++++------- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 6c8824a6a..e470b8d9d 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -249,6 +249,11 @@ namespace recfun { return m_plugin->get_def(s); } + case_def& get_case_def(expr* e) { + SASSERT(is_case_pred(e)); + return get_case_def(to_app(e)->get_name()); + } + case_def& get_case_def(symbol const & s) { SASSERT(m_plugin->has_case_def(s)); return m_plugin->get_case_def(s); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 6f62cccc0..e0b43c7ed 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -32,7 +32,7 @@ namespace smt { m(m), m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), - m_guards(m), + m_guard_preds(m), m_max_depth(0), m_q_case_expand(), m_q_body_expand() @@ -41,10 +41,6 @@ namespace smt { theory_recfun::~theory_recfun() { reset_queues(); - for (expr* g : m_guards) { - m.dec_ref(g); - } - m_guards.reset(); } char const * theory_recfun::get_name() const { return "recfun"; } @@ -112,12 +108,21 @@ namespace smt { void theory_recfun::push_scope_eh() { TRACEFN("push_scope"); theory::push_scope_eh(); + m_guard_preds_lim.push_back(m_guard_preds.size()); } void theory_recfun::pop_scope_eh(unsigned num_scopes) { TRACEFN("pop_scope " << num_scopes); theory::pop_scope_eh(num_scopes); reset_queues(); + + // restore guards + unsigned new_lim = m_guard_preds_lim.size()-num_scopes; + for (unsigned i = new_lim; i < m_guard_preds.size(); ++i) { + m_guards[m_guard_preds.get(i)->get_decl()].pop_back(); + } + m_guard_preds.resize(m_guard_preds_lim[new_lim]); + m_guard_preds_lim.shrink(new_lim); } void theory_recfun::restart_eh() { @@ -159,22 +164,20 @@ namespace smt { m_q_body_expand.clear(); } - void theory_recfun::max_depth_conflict() { - TRACEFN("max-depth conflict"); + void theory_recfun::max_depth_limit(ptr_vector const& guards) { + TRACEFN("max-depth limit"); literal_vector c; - // make clause `depth_limit => V_{g : guards} ~ g` - { - // first literal must be the depth limit one - app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); - c.push_back(~mk_literal(dlimit)); - SASSERT(ctx().get_assignment(c.back()) == l_true); - } - for (expr * g : m_guards) { - c.push_back(~ mk_literal(g)); + // make clause `depth_limit => V_{g : guards of non-recursive cases} g` + + // first literal must be the depth limit one + app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); + c.push_back(~mk_literal(dlimit)); + SASSERT(ctx().get_assignment(c.back()) == l_false); + + for (expr * g : guards) { + c.push_back(mk_literal(g)); } TRACEFN("max-depth limit: add clause " << pp_lits(ctx(), c)); - SASSERT(std::all_of(c.begin(), c.end(), [&](literal & l) { return ctx().get_assignment(l) == l_false; })); // conflict - m_q_clauses.push_back(std::move(c)); } @@ -185,22 +188,11 @@ namespace smt { void theory_recfun::assign_eh(bool_var v, bool is_true) { expr* e = ctx().bool_var2expr(v); if (is_true && u().is_case_pred(e)) { - app* a = to_app(e); TRACEFN("assign_case_pred_true " << mk_pp(e, m)); - // add to set of local assumptions, for depth-limit purpose - SASSERT(!m_guards.contains(a)); - m_guards.push_back(a); - ctx().push_trail(push_back_vector(m_guards)); - - if (m_guards.size() <= get_max_depth()) { - // body-expand - body_expansion b_e(u(), a); - push_body_expand(std::move(b_e)); - } - else { - // too many body-expansions: depth-limit conflict - max_depth_conflict(); - } + app* a = to_app(e); + // body-expand + body_expansion b_e(u(), a); + push_body_expand(std::move(b_e)); } } @@ -297,6 +289,15 @@ namespace smt { if (c.is_immediate()) { body_expansion be(c, e.m_args); assert_body_axiom(be); + + // add to set of local assumptions, for depth-limit purpose + func_decl* d = pred_applied->get_decl(); + m_guard_preds.push_back(pred_applied); + auto& vec = m_guards.insert_if_not_there2(d, ptr_vector())->get_data().m_value; + vec.push_back(pred_applied); + if (vec.size() == get_max_depth()) { + max_depth_limit(vec); + } } } } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index a2ec27314..ab09e4f45 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -70,8 +70,7 @@ namespace smt { ptr_vector m_args; body_expansion(recfun_util& u, app * n) : m_cdef(0), m_args() { - SASSERT(u.is_case_pred(n)); - m_cdef = &u.get_case_def(n->get_name()); + m_cdef = &u.get_case_def(n); m_args.append(n->get_num_args(), n->get_args()); } body_expansion(recfun_case_def const & d, ptr_vector & args) : m_cdef(&d), m_args(args) {} @@ -87,16 +86,18 @@ namespace smt { friend std::ostream& operator<<(std::ostream&, pp_body_expansion const &); - ast_manager& m; - recfun_decl_plugin& m_plugin; - recfun_util& m_util; - stats m_stats; - app_ref_vector m_guards; // true case-preds - unsigned m_max_depth; // for fairness and termination + ast_manager& m; + recfun_decl_plugin& m_plugin; + recfun_util& m_util; + stats m_stats; + obj_map > m_guards; + app_ref_vector m_guard_preds; + unsigned_vector m_guard_preds_lim; + unsigned m_max_depth; // for fairness and termination - vector m_q_case_expand; - vector m_q_body_expand; - vector m_q_clauses; + vector m_q_case_expand; + vector m_q_body_expand; + vector m_q_clauses; recfun_util & u() const { return m_util; } bool is_defined(app * f) const { return u().is_defined(f); } @@ -112,7 +113,7 @@ namespace smt { void assert_case_axioms(case_expansion & e); void assert_body_axiom(body_expansion & e); literal mk_literal(expr* e); - void max_depth_conflict(); + void max_depth_limit(ptr_vector const& guards); literal mk_eq_lit(expr* l, expr* r); bool is_standard_order(recfun::vars const& vars) const { return vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0; } protected: From 2d4a5e0a5e33847d55a6174c759cadf8f0c18d77 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 18 Oct 2018 18:07:04 -0700 Subject: [PATCH 46/65] n/a Signed-off-by: Nikolaj Bjorner --- src/ast/recfun_decl_plugin.h | 3 +++ src/smt/smt_setup.cpp | 1 - src/smt/theory_recfun.cpp | 25 ++++++------------------- src/smt/theory_recfun.h | 16 ++++++++-------- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index e470b8d9d..f2da9d053 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -201,6 +201,7 @@ namespace recfun { def* mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs); bool has_def(const symbol& s) const { return m_defs.contains(s); } + bool has_def() const { return !m_defs.empty(); } def const& get_def(const symbol& s) const { return *(m_defs[s]); } promise_def get_promise_def(const symbol &s) const { return promise_def(&u(), m_defs[s]); } def& get_def(symbol const& s) { return *(m_defs[s]); } @@ -241,6 +242,8 @@ namespace recfun { bool is_depth_limit(expr * e) const { return is_app_of(e, m_family_id, OP_DEPTH_LIMIT); } bool owns_app(app * e) const { return e->get_family_id() == m_family_id; } + bool has_def() const { return m_plugin->has_def(); } + //setup_params(); } void setup::setup_dl() { diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index e0b43c7ed..477e8c26a 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -45,7 +45,7 @@ namespace smt { char const * theory_recfun::get_name() const { return "recfun"; } - void theory_recfun::setup_params() { + void theory_recfun::init_search_eh() { // obtain max depth via parameters smt_params_helper p(ctx().get_params()); set_max_depth(p.recfun_max_depth()); @@ -330,28 +330,15 @@ namespace smt { } final_check_status theory_recfun::final_check_eh() { - if (m_guards.size() > get_max_depth()) { -#if 1 - return FC_GIVEUP; -#else - for (unsigned i = get_max_depth(); i < m_guards.size(); ++i) { - app* a = m_guards.get(i); - body_expansion b_e(u(), a); - push_body_expand(std::move(b_e)); - } - unsigned new_depth = m_guards.size() + 1; - IF_VERBOSE(2, verbose_stream() << "(smt.recfun :new-depth " << new_depth << ")\n"); - set_max_depth(new_depth); - return FC_CONTINUE; -#endif - } return FC_DONE; } void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { - app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); - TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m)); - assumptions.push_back(dlimit); + if (u().has_def()) { + app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); + TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m)); + assumptions.push_back(dlimit); + } } // if `dlimit` occurs in unsat core, return 'true' diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index ab09e4f45..265d8f305 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -138,16 +138,16 @@ namespace smt { void new_diseq_eh(theory_var v1, theory_var v2) override {} void add_theory_assumptions(expr_ref_vector & assumptions) override; + void set_max_depth(unsigned n) { SASSERT(n>0); m_max_depth = n; } + unsigned get_max_depth() const { return m_max_depth; } + public: theory_recfun(ast_manager & m); - virtual ~theory_recfun() override; - void setup_params(); // read parameters - virtual theory * mk_fresh(context * new_ctx) override; - virtual void display(std::ostream & out) const override; - virtual void collect_statistics(::statistics & st) const override; - unsigned get_max_depth() const { return m_max_depth; } - void set_max_depth(unsigned n) { SASSERT(n>0); m_max_depth = n; } - void inc_max_depth() { ++m_max_depth; } + ~theory_recfun() override; + void init_search_eh() override; + theory * mk_fresh(context * new_ctx) override; + void display(std::ostream & out) const override; + void collect_statistics(::statistics & st) const override; }; } From 936312cfd216ec7bb1b0b5e6860a79b0ee7f70e7 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Thu, 18 Oct 2018 18:15:35 -0700 Subject: [PATCH 47/65] fix location of research Signed-off-by: Nikolaj Bjorner --- src/smt/smt_context.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 4b8b1b9d9..6dbf06a2a 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3423,15 +3423,16 @@ namespace smt { if (!check_preamble(reset_cancel)) return l_undef; SASSERT(at_base_level()); setup_context(false); - expr_ref_vector asms(m_manager, num_assumptions, assumptions); - add_theory_assumptions(asms); - // introducing proxies: if (!validate_assumptions(asms)) return l_undef; - TRACE("unsat_core_bug", tout << asms << "\n";); - internalize_assertions(); - init_assumptions(asms); - TRACE("before_search", display(tout);); lbool r; do { + pop_to_base_lvl(); + expr_ref_vector asms(m_manager, num_assumptions, assumptions); + add_theory_assumptions(asms); + // introducing proxies: if (!validate_assumptions(asms)) return l_undef; + TRACE("unsat_core_bug", tout << asms << "\n";); + internalize_assertions(); + init_assumptions(asms); + TRACE("before_search", display(tout);); r = search(); r = mk_unsat_core(r); } @@ -3444,15 +3445,16 @@ namespace smt { if (!check_preamble(true)) return l_undef; TRACE("before_search", display(tout);); setup_context(false); - expr_ref_vector asms(cube); - add_theory_assumptions(asms); - // introducing proxies: if (!validate_assumptions(asms)) return l_undef; - for (auto const& clause : clauses) if (!validate_assumptions(clause)) return l_undef; - internalize_assertions(); - init_assumptions(asms); - for (auto const& clause : clauses) init_clause(clause); lbool r; do { + pop_to_base_lvl(); + expr_ref_vector asms(cube); + add_theory_assumptions(asms); + // introducing proxies: if (!validate_assumptions(asms)) return l_undef; + for (auto const& clause : clauses) if (!validate_assumptions(clause)) return l_undef; + internalize_assertions(); + init_assumptions(asms); + for (auto const& clause : clauses) init_clause(clause); r = search(); r = mk_unsat_core(r); } From eb15f8249ab79e16d891d2040d9728f9f852b4ec Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 19 Oct 2018 21:01:25 -0700 Subject: [PATCH 48/65] fix backtrack Signed-off-by: Nikolaj Bjorner --- src/smt/theory_recfun.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 477e8c26a..059a34f29 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -118,10 +118,11 @@ namespace smt { // restore guards unsigned new_lim = m_guard_preds_lim.size()-num_scopes; - for (unsigned i = new_lim; i < m_guard_preds.size(); ++i) { + unsigned start = m_guard_preds_lim[new_lim]; + for (unsigned i = start; i < m_guard_preds.size(); ++i) { m_guards[m_guard_preds.get(i)->get_decl()].pop_back(); } - m_guard_preds.resize(m_guard_preds_lim[new_lim]); + m_guard_preds.resize(start); m_guard_preds_lim.shrink(new_lim); } From 6e41b853f7e2dd8abcc60310d3a1237c0f61dca0 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 21 Oct 2018 12:25:57 -0700 Subject: [PATCH 49/65] remove case-pred and depth-limit classes Signed-off-by: Nikolaj Bjorner --- src/ast/array_decl_plugin.cpp | 1 + src/ast/recfun_decl_plugin.cpp | 60 +++++++------------------- src/ast/recfun_decl_plugin.h | 64 ++++------------------------ src/cmd_context/cmd_context.cpp | 11 +++-- src/parsers/smt2/smt2parser.cpp | 38 ++++++++++++++--- src/smt/params/smt_params_helper.pyg | 2 +- src/smt/smt_setup.cpp | 1 + src/smt/theory_recfun.cpp | 28 ++++++------ src/smt/theory_recfun.h | 1 - 9 files changed, 81 insertions(+), 125 deletions(-) diff --git a/src/ast/array_decl_plugin.cpp b/src/ast/array_decl_plugin.cpp index af451f9c5..868b35d00 100644 --- a/src/ast/array_decl_plugin.cpp +++ b/src/ast/array_decl_plugin.cpp @@ -517,6 +517,7 @@ func_decl * array_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters void array_decl_plugin::get_sort_names(svector& sort_names, symbol const & logic) { sort_names.push_back(builtin_name(ARRAY_SORT_STR, ARRAY_SORT)); + sort_names.push_back(builtin_name("=>", ARRAY_SORT)); // TBD: this could easily break users even though it is already used in CVC4: // sort_names.push_back(builtin_name("Set", _SET_SORT)); } diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 1cd9a4fb8..cdd1cec92 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -27,13 +27,7 @@ Revision History: #define VALIDATE_PARAM(m, _pred_) if (!(_pred_)) m.raise_exception("invalid parameter to recfun " #_pred_); namespace recfun { - case_pred::case_pred(ast_manager & m, family_id fid, std::string const & s, sort_ref_vector const & domain) - : m_name(), m_name_buf(s), m_domain(domain), m_decl(m) - { - m_name = symbol(m_name_buf.c_str()); - func_decl_info info(fid, OP_FUN_CASE_PRED); - m_decl = m.mk_func_decl(m_name, domain.size(), domain.c_ptr(), m.mk_bool_sort(), info); - } + case_def::case_def(ast_manager &m, family_id fid, @@ -41,21 +35,20 @@ namespace recfun { std::string & name, sort_ref_vector const & arg_sorts, unsigned num_guards, expr ** guards, expr* rhs) - : m_pred(m, fid, name, arg_sorts), m_guards(m), m_rhs(expr_ref(rhs,m)), m_def(d) { - for (unsigned i = 0; i < num_guards; ++i) { - m_guards.push_back(guards[i]); - } + : m_pred(m), + m_guards(m, num_guards, guards), + m_rhs(expr_ref(rhs,m)), + m_def(d) { + func_decl_info info(fid, OP_FUN_CASE_PRED); + m_pred = m.mk_func_decl(symbol(name.c_str()), arg_sorts.size(), arg_sorts.c_ptr(), m.mk_bool_sort(), info); } def::def(ast_manager &m, family_id fid, symbol const & s, unsigned arity, sort* const * domain, sort* range) : m(m), m_name(s), - m_domain(m), m_range(range, m), m_vars(m), m_cases(), + m_domain(m, arity, domain), m_range(range, m), m_vars(m), m_cases(), m_decl(m), m_fid(fid), m_macro(false) { - for (unsigned i = 0; i < arity; ++i) - m_domain.push_back(domain[i]); - SASSERT(arity == get_arity()); func_decl_info info(fid, OP_FUN_DEFINED); @@ -199,10 +192,11 @@ namespace recfun { void def::compute_cases(is_immediate_pred & is_i, th_rewriter & th_rw, unsigned n_vars, var *const * vars, expr* rhs0) { - if (m_cases.size() != 0) { + if (!m_cases.empty()) { TRACEFN("bug: " << m_name << " has cases already"); UNREACHABLE(); } + SASSERT(m_cases.empty()); SASSERT(n_vars == m_domain.size()); TRACEFN("compute cases " << mk_pp(rhs0, m)); @@ -343,14 +337,11 @@ namespace recfun { */ util::util(ast_manager & m, family_id id) - : m_manager(m), m_family_id(id), m_th_rw(m), m_plugin(0), m_dlimit_map() { + : m_manager(m), m_family_id(id), m_th_rw(m), m_plugin(0) { m_plugin = dynamic_cast(m.get_plugin(m_family_id)); } util::~util() { - for (auto & kv : m_dlimit_map) { - dealloc(kv.m_value); - } } def * util::decl_fun(symbol const& name, unsigned n, sort *const * domain, sort * range) { @@ -361,20 +352,11 @@ namespace recfun { d.set_definition(n_vars, vars, rhs); } - // get or create predicate for depth limit - depth_limit_pred_ref util::get_depth_limit_pred(unsigned d) { - depth_limit_pred * pred = nullptr; - if (! m_dlimit_map.find(d, pred)) { - pred = alloc(depth_limit_pred, m(), m_family_id, d); - m_dlimit_map.insert(d, pred); - } - return depth_limit_pred_ref(pred, *this); - } - app_ref util::mk_depth_limit_pred(unsigned d) { - depth_limit_pred_ref p = get_depth_limit_pred(d); - app_ref res(m().mk_const(p->get_decl()), m()); - return res; + parameter p(d); + func_decl_info info(m_family_id, OP_DEPTH_LIMIT, 1, &p); + func_decl* decl = m().mk_const_decl(symbol("recfun-depth-limit"), m().mk_bool_sort(), info); + return app_ref(m().mk_const(decl), m()); } // used to know which `app` are from this theory @@ -409,18 +391,6 @@ namespace recfun { d->compute_cases(is_i, u->get_th_rewriter(), n_vars, vars, rhs); } - depth_limit_pred::depth_limit_pred(ast_manager & m, family_id fid, unsigned d) - : m_name_buf(), m_name(""), m_depth(d), m_decl(m) { - // build name, then build decl - std::ostringstream tmpname; - tmpname << "depth_limit_" << d << std::flush; - m_name_buf.append(tmpname.str()); - m_name = symbol(m_name_buf.c_str()); - parameter params[1] = { parameter(d) }; - func_decl_info info(fid, OP_DEPTH_LIMIT, 1, params); - m_decl = m.mk_const_decl(m_name, m.mk_bool_sort(), info); - } - namespace decl { plugin::plugin() : decl_plugin(), m_defs(), m_case_defs(), m_def_block() {} plugin::~plugin() { finalize(); } diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index f2da9d053..cadbccfec 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -40,26 +40,12 @@ namespace recfun { and therefore two case predicates `C_fact_0` and `C_fact_1`, where `C_fact_0(t)=true` means `t<2` (first path) and `C_fact_1(t)=true` means `not (t<2)` (second path). */ - class case_pred { - friend class case_def; - symbol m_name; //get_name(); } + + app_ref apply_case_predicate(ptr_vector const & args) const { + ast_manager& m = m_pred.get_manager(); + return app_ref(m.mk_app(m_pred, args.size(), args.c_ptr()), m); + } + def * get_def() const { return m_def; } expr_ref_vector const & get_guards() const { return m_guards; } expr * get_guards_c_ptr() const { return *m_guards.c_ptr(); } @@ -145,27 +136,6 @@ namespace recfun { def * get_def() const { return d; } }; - // predicate for limiting unrolling depth, to be used in assumptions and conflicts - class depth_limit_pred { - friend class util; - std::string m_name_buf; - symbol m_name; - unsigned m_depth; - func_decl_ref m_decl; - unsigned m_refcount; - - void inc_ref() { m_refcount ++; } - void dec_ref() { SASSERT(m_refcount > 0); m_refcount --; } - public: - depth_limit_pred(ast_manager & m, family_id fid, unsigned d); - unsigned get_depth() const { return m_depth; } - symbol const & get_name() const { return m_name; } - func_decl * get_decl() const { return m_decl.get(); } - }; - - // A reference to `depth_limit_pred` - typedef obj_ref depth_limit_pred_ref; - namespace decl { class plugin : public decl_plugin { @@ -220,17 +190,15 @@ namespace recfun { // Varus utils for recursive functions class util { friend class decl::plugin; - - typedef map> depth_limit_map; ast_manager & m_manager; family_id m_family_id; th_rewriter m_th_rw; decl::plugin * m_plugin; - depth_limit_map m_dlimit_map; bool compute_is_immediate(expr * rhs); void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); + public: util(ast_manager &m, family_id); virtual ~util(); @@ -268,26 +236,12 @@ namespace recfun { app* mk_fun_defined(def const & d, ptr_vector const & args) { return mk_fun_defined(d, args.size(), args.c_ptr()); } - app* mk_case_pred(case_pred const & p, ptr_vector const & args) { - return m().mk_app(p.get_decl(), args.size(), args.c_ptr()); - } - void inc_ref(depth_limit_pred * p) { p->inc_ref(); } - void dec_ref(depth_limit_pred * p) { - p->dec_ref(); - if (p->m_refcount == 0) { - m_dlimit_map.remove(p->m_depth); - dealloc(p); - } - } - - depth_limit_pred_ref get_depth_limit_pred(unsigned d); app_ref mk_depth_limit_pred(unsigned d); }; } typedef recfun::def recfun_def; typedef recfun::case_def recfun_case_def; -typedef recfun::depth_limit_pred recfun_depth_limit_pred; typedef recfun::decl::plugin recfun_decl_plugin; typedef recfun::util recfun_util; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 18b47d012..188e2331e 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -1135,14 +1135,17 @@ void cmd_context::mk_app(symbol const & s, unsigned num_args, expr * const * arg if (num_args == 0 && range == nullptr) { if (fs.more_than_one()) - throw cmd_exception("ambiguous constant reference, more than one constant with the same sort, use a qualified expression (as ) to disumbiguate ", s); + throw cmd_exception("ambiguous constant reference, more than one constant with the same sort, use a qualified expression (as ) to disambiguate ", s); func_decl * f = fs.first(); if (f == nullptr) { throw cmd_exception("unknown constant ", s); } - if (f->get_arity() != 0) - throw cmd_exception("invalid function application, missing arguments ", s); - result = m().mk_const(f); + if (f->get_arity() != 0) { + result = array_util(m()).mk_as_array(f); + } + else { + result = m().mk_const(f); + } } else { func_decl * f = fs.find(m(), num_args, args, range); diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 7458fc578..e60608ad3 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -1439,6 +1439,12 @@ namespace smt2 { // compute match condition and substitution // t is shifted by size of subst. expr_ref bind_match(expr* t, expr* pattern, expr_ref_vector& subst) { + if (m().get_sort(t) != m().get_sort(pattern)) { + std::ostringstream str; + str << "sorts of pattern " << expr_ref(pattern, m()) << " and term " + << expr_ref(t, m()) << " are not aligned"; + throw parser_exception(str.str()); + } expr_ref tsh(m()); if (is_var(pattern)) { shifter()(t, 1, tsh); @@ -1894,13 +1900,31 @@ namespace smt2 { unsigned num_args = expr_stack().size() - fr->m_expr_spos; unsigned num_indices = m_param_stack.size() - fr->m_param_spos; expr_ref t_ref(m()); - m_ctx.mk_app(fr->m_f, - num_args, - expr_stack().c_ptr() + fr->m_expr_spos, - num_indices, - m_param_stack.c_ptr() + fr->m_param_spos, - fr->m_as_sort ? sort_stack().back() : nullptr, - t_ref); + local l; + if (m_env.find(fr->m_f, l)) { + push_local(l); + t_ref = expr_stack().back(); + for (unsigned i = 0; i < num_args; ++i) { + expr* arg = expr_stack().get(fr->m_expr_spos + i); + expr* args[2] = { t_ref.get(), arg }; + m_ctx.mk_app(symbol("select"), + 2, + args, + 0, + nullptr, + nullptr, + t_ref); + } + } + else { + m_ctx.mk_app(fr->m_f, + num_args, + expr_stack().c_ptr() + fr->m_expr_spos, + num_indices, + m_param_stack.c_ptr() + fr->m_param_spos, + fr->m_as_sort ? sort_stack().back() : nullptr, + t_ref); + } expr_stack().shrink(fr->m_expr_spos); m_param_stack.shrink(fr->m_param_spos); if (fr->m_as_sort) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 828e4f7de..c927b3214 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -97,5 +97,5 @@ def_module_params(module_name='smt', ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), ('dt_lazy_splits', UINT, 1, 'How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy'), ('recfun.native', BOOL, False, 'use native rec-fun solver'), - ('recfun.max_depth', UINT, 500, 'maximum depth of unrolling for recursive functions') + ('recfun.max_depth', UINT, 50, 'maximum depth of unrolling for recursive functions') )) diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 5b02c5bbb..99ec09d1d 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -964,6 +964,7 @@ namespace smt { setup_seq_str(st); setup_card(); setup_fpa(); + setup_recfuns(); return; } diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 059a34f29..0a91074ec 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -83,6 +83,7 @@ namespace smt { void theory_recfun::reset_queues() { m_q_case_expand.reset(); m_q_body_expand.reset(); + m_q_clauses.clear(); } void theory_recfun::reset_eh() { @@ -146,7 +147,8 @@ namespace smt { } m_q_clauses.clear(); - for (case_expansion & e : m_q_case_expand) { + for (unsigned i = 0; i < m_q_case_expand.size(); ++i) { + case_expansion & e = m_q_case_expand[i]; if (e.m_def->is_fun_macro()) { // body expand immediately assert_macro_axiom(e); @@ -159,8 +161,8 @@ namespace smt { } m_q_case_expand.clear(); - for (body_expansion & e : m_q_body_expand) { - assert_body_axiom(e); + for (unsigned i = 0; i < m_q_body_expand.size(); ++i) { + assert_body_axiom(m_q_body_expand[i]); } m_q_body_expand.clear(); } @@ -173,6 +175,8 @@ namespace smt { // first literal must be the depth limit one app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); c.push_back(~mk_literal(dlimit)); + enable_trace("recfun"); + TRACE("recfun", ctx().display(tout << c.back() << " " << dlimit << "\n");); SASSERT(ctx().get_assignment(c.back()) == l_false); for (expr * g : guards) { @@ -208,12 +212,7 @@ namespace smt { ctx().get_rewriter()(new_body); // simplify return new_body; } - - app_ref theory_recfun::apply_pred(recfun::case_pred const & p, - ptr_vector const & args) { - return app_ref(u().mk_case_pred(p, args), m); - } - + literal theory_recfun::mk_literal(expr* e) { ctx().internalize(e, false); literal lit = ctx().get_literal(e); @@ -246,14 +245,14 @@ namespace smt { * 2. add unit clause `f(args) = rhs` */ void theory_recfun::assert_macro_axiom(case_expansion & e) { + TRACEFN("case expansion " << pp_case_expansion(e, m) << "\n"); SASSERT(e.m_def->is_fun_macro()); auto & vars = e.m_def->get_vars(); expr_ref lhs(e.m_lhs, m); expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m); literal lit = mk_eq_lit(lhs, rhs); ctx().mk_th_axiom(get_id(), 1, &lit); - TRACEFN("case expansion " << pp_case_expansion(e, m) << "\n" << - "macro expansion yields " << mk_pp(rhs,m) << "\n" << + TRACEFN("macro expansion yields " << mk_pp(rhs,m) << "\n" << "literal " << pp_lit(ctx(), lit)); } @@ -272,7 +271,7 @@ namespace smt { auto & vars = e.m_def->get_vars(); for (recfun::case_def const & c : e.m_def->get_cases()) { // applied predicate to `args` - app_ref pred_applied = apply_pred(c.get_pred(), e.m_args); + app_ref pred_applied = c.apply_case_predicate(e.m_args); SASSERT(u().owns_app(pred_applied)); literal concl = mk_literal(pred_applied); @@ -331,6 +330,11 @@ namespace smt { } final_check_status theory_recfun::final_check_eh() { + TRACEFN("final\n"); + if (can_propagate()) { + propagate(); + return FC_CONTINUE; + } return FC_DONE; } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 265d8f305..f4783dcdc 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -108,7 +108,6 @@ namespace smt { void reset_queues(); expr_ref apply_args(recfun::vars const & vars, ptr_vector const & args, expr * e); //!< substitute variables by args - app_ref apply_pred(recfun::case_pred const & p, ptr_vector const & args); // Date: Sun, 21 Oct 2018 13:15:51 -0700 Subject: [PATCH 50/65] updates to recfun_decl_plugin Signed-off-by: Nikolaj Bjorner --- src/ast/recfun_decl_plugin.cpp | 107 +++++++++++++-------------------- src/ast/recfun_decl_plugin.h | 6 +- 2 files changed, 44 insertions(+), 69 deletions(-) diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index cdd1cec92..6ee0ddc39 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -33,26 +33,32 @@ namespace recfun { family_id fid, def * d, std::string & name, + unsigned case_index, sort_ref_vector const & arg_sorts, - unsigned num_guards, expr ** guards, expr* rhs) + expr_ref_vector const& guards, + expr* rhs) : m_pred(m), - m_guards(m, num_guards, guards), + m_guards(guards), m_rhs(expr_ref(rhs,m)), m_def(d) { - func_decl_info info(fid, OP_FUN_CASE_PRED); + parameter p(case_index); + func_decl_info info(fid, OP_FUN_CASE_PRED, 1, &p); m_pred = m.mk_func_decl(symbol(name.c_str()), arg_sorts.size(), arg_sorts.c_ptr(), m.mk_bool_sort(), info); } def::def(ast_manager &m, family_id fid, symbol const & s, unsigned arity, sort* const * domain, sort* range) : m(m), m_name(s), - m_domain(m, arity, domain), m_range(range, m), m_vars(m), m_cases(), - m_decl(m), m_fid(fid), m_macro(false) + m_domain(m, arity, domain), + m_range(range, m), m_vars(m), m_cases(), + m_decl(m), + m_fid(fid), + m_macro(false) { SASSERT(arity == get_arity()); func_decl_info info(fid, OP_FUN_DEFINED); - m_decl = m.mk_func_decl(m_name, arity, domain, range, info); + m_decl = m.mk_func_decl(s, arity, domain, range, info); } // does `e` contain any `ite` construct? @@ -101,6 +107,8 @@ namespace recfun { ite_lst const * to_split; // `ite` terms to make a choice on unfold_lst const * to_unfold; // terms yet to unfold + branch(unfold_lst const * to_unfold): + path(nullptr), to_split(nullptr), to_unfold(to_unfold) {} branch(choice_lst const * path, ite_lst const * to_split, unfold_lst const * to_unfold) : path(path), to_split(to_split), to_unfold(to_unfold) {} branch(branch const & from) : @@ -110,14 +118,12 @@ namespace recfun { // state for computing cases from the RHS of a functions' definition class case_state { region m_reg; - ast_manager & m_manager; vector m_branches; public: - case_state(ast_manager & m) : m_reg(), m_manager(m), m_branches() {} + case_state() : m_reg(), m_branches() {} bool empty() const { return m_branches.empty(); } - ast_manager & m() const { return m_manager; } region & reg() { return m_reg; } branch pop_branch() { @@ -128,7 +134,6 @@ namespace recfun { void push_branch(branch const & b) { m_branches.push_back(b); } - unfold_lst const * cons_unfold(expr * e, unfold_lst const * next) { return new (reg()) unfold_lst{e, next}; } @@ -149,7 +154,7 @@ namespace recfun { }; // Date: Sun, 21 Oct 2018 18:25:27 -0700 Subject: [PATCH 51/65] recfun Signed-off-by: Nikolaj Bjorner --- src/api/python/z3/z3.py | 1 + src/ast/recfun_decl_plugin.cpp | 60 ++++++++++++++-------------- src/ast/recfun_decl_plugin.h | 31 ++++++-------- src/smt/params/smt_params_helper.pyg | 2 +- src/smt/smt_context.h | 5 ++- src/smt/theory_recfun.cpp | 44 +++++++++++--------- src/smt/theory_recfun.h | 5 +-- 7 files changed, 75 insertions(+), 73 deletions(-) diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index d466d2e77..63b5a9e72 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -1,4 +1,5 @@ + ############################################ # Copyright (c) 2012 Microsoft Corporation # diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 6ee0ddc39..e5796b119 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -15,6 +15,7 @@ Revision History: --*/ + #include #include #include "ast/expr_functors.h" @@ -29,14 +30,15 @@ Revision History: namespace recfun { - case_def::case_def(ast_manager &m, - family_id fid, - def * d, - std::string & name, - unsigned case_index, - sort_ref_vector const & arg_sorts, - expr_ref_vector const& guards, - expr* rhs) + case_def::case_def( + ast_manager &m, + family_id fid, + def * d, + std::string & name, + unsigned case_index, + sort_ref_vector const & arg_sorts, + expr_ref_vector const& guards, + expr* rhs) : m_pred(m), m_guards(guards), m_rhs(expr_ref(rhs,m)), @@ -52,11 +54,9 @@ namespace recfun { m_domain(m, arity, domain), m_range(range, m), m_vars(m), m_cases(), m_decl(m), - m_fid(fid), - m_macro(false) + m_fid(fid) { - SASSERT(arity == get_arity()); - + SASSERT(arity == get_arity()); func_decl_info info(fid, OP_FUN_DEFINED); m_decl = m.mk_func_decl(s, arity, domain, range, info); } @@ -124,7 +124,6 @@ namespace recfun { case_state() : m_reg(), m_branches() {} bool empty() const { return m_branches.empty(); } - region & reg() { return m_reg; } branch pop_branch() { branch res = m_branches.back(); @@ -135,7 +134,7 @@ namespace recfun { void push_branch(branch const & b) { m_branches.push_back(b); } unfold_lst const * cons_unfold(expr * e, unfold_lst const * next) { - return new (reg()) unfold_lst{e, next}; + return new (m_reg) unfold_lst{e, next}; } unfold_lst const * cons_unfold(expr * e1, expr * e2, unfold_lst const * next) { return cons_unfold(e1, cons_unfold(e2, next)); @@ -145,11 +144,11 @@ namespace recfun { } ite_lst const * cons_ite(app * ite, ite_lst const * next) { - return new (reg()) ite_lst{ite, next}; + return new (m_reg) ite_lst{ite, next}; } choice_lst const * cons_choice(app * ite, bool sign, choice_lst const * next) { - return new (reg()) choice_lst{ite, sign, next}; + return new (m_reg) choice_lst{ite, sign, next}; } }; @@ -203,21 +202,17 @@ namespace recfun { unsigned case_idx = 0; - std::string name; - name.append("case_"); + std::string name("case-"); name.append(m_name.bare_str()); - name.append("_"); m_vars.append(n_vars, vars); - // is the function a macro (unconditional body)? - m_macro = n_vars == 0 || !contains_ite(rhs); expr_ref_vector conditions(m); - if (m_macro) { + // is the function a macro (unconditional body)? + if (n_vars == 0 || !contains_ite(rhs)) { // constant function or trivial control flow, only one (dummy) case - name.append("dummy"); add_case(name, 0, conditions, rhs); return; } @@ -311,15 +306,15 @@ namespace recfun { */ util::util(ast_manager & m, family_id id) - : m_manager(m), m_family_id(id), m_th_rw(m), m_plugin(0) { - m_plugin = dynamic_cast(m.get_plugin(m_family_id)); + : m_manager(m), m_fid(id), m_th_rw(m), + m_plugin(dynamic_cast(m.get_plugin(m_fid))) { } util::~util() { } def * util::decl_fun(symbol const& name, unsigned n, sort *const * domain, sort * range) { - return alloc(def, m(), m_family_id, name, n, domain, range); + return alloc(def, m(), m_fid, name, n, domain, range); } void util::set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs) { @@ -328,7 +323,7 @@ namespace recfun { app_ref util::mk_depth_limit_pred(unsigned d) { parameter p(d); - func_decl_info info(m_family_id, OP_DEPTH_LIMIT, 1, &p); + func_decl_info info(m_fid, OP_DEPTH_LIMIT, 1, &p); func_decl* decl = m().mk_const_decl(symbol("recfun-depth-limit"), m().mk_bool_sort(), info); return app_ref(m().mk_const(decl), m()); } @@ -376,13 +371,13 @@ namespace recfun { m_defs.reset(); // m_case_defs does not own its data, no need to deallocate m_case_defs.reset(); - m_util = 0; // force deletion + m_util = nullptr; // force deletion } util & plugin::u() const { SASSERT(m_manager); SASSERT(m_family_id != null_family_id); - if (m_util.get() == 0) { + if (!m_util.get()) { m_util = alloc(util, *m_manager, m_family_id); } return *(m_util.get()); @@ -398,7 +393,7 @@ namespace recfun { void plugin::set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs) { u().set_definition(d, n_vars, vars, rhs); for (case_def & c : d.get_def()->get_cases()) { - m_case_defs.insert(c.get_name(), &c); + m_case_defs.insert(c.get_decl(), &c); } } @@ -434,7 +429,10 @@ namespace recfun { func_decl * plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range) { - switch(k) { + UNREACHABLE(); + // TBD: parameter usage seems inconsistent with other uses. + IF_VERBOSE(0, verbose_stream() << "mk-func-decl " << k << "\n"); + switch (k) { case OP_FUN_CASE_PRED: return mk_fun_pred_decl(num_parameters, parameters, arity, domain, range); case OP_FUN_DEFINED: diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 2bfae9e4e..1e6f873e2 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -19,6 +19,7 @@ Revision History: #include "ast/ast.h" #include "ast/rewriter/th_rewriter.h" +#include "util/obj_hashtable.h" namespace recfun { class case_def; //get_name(); } + func_decl* get_decl() const { return m_pred; } app_ref apply_case_predicate(ptr_vector const & args) const { ast_manager& m = m_pred.get_manager(); @@ -97,7 +98,6 @@ namespace recfun { cases m_cases; //!< possible cases func_decl_ref m_decl; //!< generic declaration family_id m_fid; - bool m_macro; def(ast_manager &m, family_id fid, symbol const & s, unsigned arity, sort *const * domain, sort* range); @@ -115,11 +115,10 @@ namespace recfun { sort_ref const & get_range() const { return m_range; } func_decl * get_decl() const { return m_decl.get(); } - bool is_fun_macro() const { return m_macro; } + bool is_fun_macro() const { return m_cases.size() == 1; } bool is_fun_defined() const { return !is_fun_macro(); } expr * get_macro_rhs() const { - SASSERT(is_fun_macro()); return m_cases[0].get_rhs(); } }; @@ -140,7 +139,7 @@ namespace recfun { class plugin : public decl_plugin { typedef map def_map; - typedef map case_def_map; + typedef obj_map case_def_map; mutable scoped_ptr m_util; def_map m_defs; // function->def @@ -175,8 +174,8 @@ namespace recfun { def const& get_def(const symbol& s) const { return *(m_defs[s]); } promise_def get_promise_def(const symbol &s) const { return promise_def(&u(), m_defs[s]); } def& get_def(symbol const& s) { return *(m_defs[s]); } - bool has_case_def(const symbol& s) const { return m_case_defs.contains(s); } - case_def& get_case_def(symbol const& s) { SASSERT(has_case_def(s)); return *(m_case_defs[s]); } + bool has_case_def(func_decl* f) const { return m_case_defs.contains(f); } + case_def& get_case_def(func_decl* f) { SASSERT(has_case_def(f)); return *(m_case_defs[f]); } bool is_declared(symbol const& s) const { return m_defs.contains(s); } private: func_decl * mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, @@ -192,7 +191,7 @@ namespace recfun { friend class decl::plugin; ast_manager & m_manager; - family_id m_family_id; + family_id m_fid; th_rewriter m_th_rw; decl::plugin * m_plugin; @@ -205,10 +204,10 @@ namespace recfun { ast_manager & m() { return m_manager; } th_rewriter & get_th_rewriter() { return m_th_rw; } - bool is_case_pred(expr * e) const { return is_app_of(e, m_family_id, OP_FUN_CASE_PRED); } - bool is_defined(expr * e) const { return is_app_of(e, m_family_id, OP_FUN_DEFINED); } - bool is_depth_limit(expr * e) const { return is_app_of(e, m_family_id, OP_DEPTH_LIMIT); } - bool owns_app(app * e) const { return e->get_family_id() == m_family_id; } + bool is_case_pred(expr * e) const { return is_app_of(e, m_fid, OP_FUN_CASE_PRED); } + bool is_defined(expr * e) const { return is_app_of(e, m_fid, OP_FUN_DEFINED); } + bool is_depth_limit(expr * e) const { return is_app_of(e, m_fid, OP_DEPTH_LIMIT); } + bool owns_app(app * e) const { return e->get_family_id() == m_fid; } bool has_def() const { return m_plugin->has_def(); } @@ -222,17 +221,13 @@ namespace recfun { case_def& get_case_def(expr* e) { SASSERT(is_case_pred(e)); - return get_case_def(to_app(e)->get_name()); - } - - case_def& get_case_def(symbol const & s) { - SASSERT(m_plugin->has_case_def(s)); - return m_plugin->get_case_def(s); + return m_plugin->get_case_def(to_app(e)->get_decl()); } app* mk_fun_defined(def const & d, unsigned n_args, expr * const * args) { return m().mk_app(d.get_decl(), n_args, args); } + app* mk_fun_defined(def const & d, ptr_vector const & args) { return mk_fun_defined(d, args.size(), args.c_ptr()); } diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 52e8f664c..37ed061b4 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -97,5 +97,5 @@ def_module_params(module_name='smt', ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), ('dt_lazy_splits', UINT, 1, 'How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy'), ('recfun.native', BOOL, False, 'use native rec-fun solver'), - ('recfun.max_depth', UINT, 50, 'maximum depth of unrolling for recursive functions') + ('recfun.max_depth', UINT, 2, 'maximum depth of unrolling for recursive functions') )) diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 45341d368..f628b6e95 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -1639,13 +1639,14 @@ namespace smt { }; inline std::ostream & operator<<(std::ostream & out, pp_lits const & pp) { - out << "clause{"; + out << "{"; bool first = true; for (unsigned i = 0; i < pp.len; ++i) { - if (first) { first = false; } else { out << " ∨ "; } + if (first) { first = false; } else { out << " or\n"; } pp.ctx.display_detailed_literal(out, pp.lits[i]); } return out << "}"; + } }; diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 0a91074ec..79060c631 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -33,7 +33,7 @@ namespace smt { m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), m_guard_preds(m), - m_max_depth(0), + m_max_depth(UINT_MAX), m_q_case_expand(), m_q_body_expand() { @@ -45,10 +45,12 @@ namespace smt { char const * theory_recfun::get_name() const { return "recfun"; } - void theory_recfun::init_search_eh() { - // obtain max depth via parameters - smt_params_helper p(ctx().get_params()); - set_max_depth(p.recfun_max_depth()); + unsigned theory_recfun::get_max_depth() { + if (m_max_depth == UINT_MAX) { + smt_params_helper p(ctx().get_params()); + set_max_depth(p.recfun_max_depth()); + } + return m_max_depth; } theory* theory_recfun::mk_fresh(context* new_ctx) { @@ -121,7 +123,7 @@ namespace smt { unsigned new_lim = m_guard_preds_lim.size()-num_scopes; unsigned start = m_guard_preds_lim[new_lim]; for (unsigned i = start; i < m_guard_preds.size(); ++i) { - m_guards[m_guard_preds.get(i)->get_decl()].pop_back(); + m_guards[m_guard_preds.get(i)].pop_back(); } m_guard_preds.resize(start); m_guard_preds_lim.shrink(new_lim); @@ -177,7 +179,6 @@ namespace smt { c.push_back(~mk_literal(dlimit)); enable_trace("recfun"); TRACE("recfun", ctx().display(tout << c.back() << " " << dlimit << "\n");); - SASSERT(ctx().get_assignment(c.back()) == l_false); for (expr * g : guards) { c.push_back(mk_literal(g)); @@ -194,17 +195,17 @@ namespace smt { expr* e = ctx().bool_var2expr(v); if (is_true && u().is_case_pred(e)) { TRACEFN("assign_case_pred_true " << mk_pp(e, m)); - app* a = to_app(e); // body-expand - body_expansion b_e(u(), a); + body_expansion b_e(u(), to_app(e)); push_body_expand(std::move(b_e)); } } // replace `vars` by `args` in `e` - expr_ref theory_recfun::apply_args(recfun::vars const & vars, - ptr_vector const & args, - expr * e) { + expr_ref theory_recfun::apply_args( + recfun::vars const & vars, + ptr_vector const & args, + expr * e) { SASSERT(is_standard_order(vars)); var_subst subst(m, true); expr_ref new_body(m); @@ -245,14 +246,14 @@ namespace smt { * 2. add unit clause `f(args) = rhs` */ void theory_recfun::assert_macro_axiom(case_expansion & e) { - TRACEFN("case expansion " << pp_case_expansion(e, m) << "\n"); + TRACEFN("case expansion " << pp_case_expansion(e, m) << "\n"); SASSERT(e.m_def->is_fun_macro()); auto & vars = e.m_def->get_vars(); expr_ref lhs(e.m_lhs, m); expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m); literal lit = mk_eq_lit(lhs, rhs); ctx().mk_th_axiom(get_id(), 1, &lit); - TRACEFN("macro expansion yields " << mk_pp(rhs,m) << "\n" << + TRACEFN("macro expansion yields " << mk_pp(rhs, m) << "\n" << "literal " << pp_lit(ctx(), lit)); } @@ -291,9 +292,10 @@ namespace smt { assert_body_axiom(be); // add to set of local assumptions, for depth-limit purpose - func_decl* d = pred_applied->get_decl(); + + // func_decl* d = pred_applied->get_decl(); m_guard_preds.push_back(pred_applied); - auto& vec = m_guards.insert_if_not_there2(d, ptr_vector())->get_data().m_value; + auto& vec = m_guards.insert_if_not_there2(e.m_lhs, ptr_vector())->get_data().m_value; vec.push_back(pred_applied); if (vec.size() == get_max_depth()) { max_depth_limit(vec); @@ -322,11 +324,17 @@ namespace smt { for (auto & g : e.m_cdef->get_guards()) { expr_ref guard = apply_args(vars, args, g); clause.push_back(~mk_literal(guard)); + if (clause.back() == true_literal) { + return; + } + if (clause.back() == false_literal) { + clause.pop_back(); + } } clause.push_back(mk_eq_lit(lhs, rhs)); ctx().mk_th_axiom(get_id(), clause); TRACEFN("body " << pp_body_expansion(e,m)); - TRACEFN("clause " << pp_lits(ctx(), clause)); + TRACEFN(pp_lits(ctx(), clause)); } final_check_status theory_recfun::final_check_eh() { @@ -373,7 +381,7 @@ namespace smt { } std::ostream& operator<<(std::ostream & out, theory_recfun::pp_body_expansion const & e) { - out << "body_exp(" << e.e.m_cdef->get_name(); + out << "body_exp(" << e.e.m_cdef->get_decl()->get_name(); for (auto* t : e.e.m_args) { out << " " << mk_pp(t,e.m); } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index f4783dcdc..68e2609d7 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -90,7 +90,7 @@ namespace smt { recfun_decl_plugin& m_plugin; recfun_util& m_util; stats m_stats; - obj_map > m_guards; + obj_map > m_guards; app_ref_vector m_guard_preds; unsigned_vector m_guard_preds_lim; unsigned m_max_depth; // for fairness and termination @@ -138,12 +138,11 @@ namespace smt { void add_theory_assumptions(expr_ref_vector & assumptions) override; void set_max_depth(unsigned n) { SASSERT(n>0); m_max_depth = n; } - unsigned get_max_depth() const { return m_max_depth; } + unsigned get_max_depth(); public: theory_recfun(ast_manager & m); ~theory_recfun() override; - void init_search_eh() override; theory * mk_fresh(context * new_ctx) override; void display(std::ostream & out) const override; void collect_statistics(::statistics & st) const override; From cd9c752834b36c033324600bdd094e62b4cda849 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 21 Oct 2018 20:46:12 -0700 Subject: [PATCH 52/65] guard Signed-off-by: Nikolaj Bjorner --- src/smt/theory_recfun.cpp | 95 +++++++++++++++++++++++++-------------- src/smt/theory_recfun.h | 48 ++++++++++++-------- 2 files changed, 91 insertions(+), 52 deletions(-) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 79060c631..bd6f7c3fa 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -19,6 +19,7 @@ Revision History: #include "util/stats.h" #include "ast/ast_util.h" +#include "ast/for_each_expr.h" #include "smt/theory_recfun.h" #include "smt/params/smt_params_helper.hpp" @@ -32,7 +33,7 @@ namespace smt { m(m), m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), - m_guard_preds(m), + m_preds(m), m_max_depth(UINT_MAX), m_q_case_expand(), m_q_body_expand() @@ -111,7 +112,7 @@ namespace smt { void theory_recfun::push_scope_eh() { TRACEFN("push_scope"); theory::push_scope_eh(); - m_guard_preds_lim.push_back(m_guard_preds.size()); + m_preds_lim.push_back(m_preds.size()); } void theory_recfun::pop_scope_eh(unsigned num_scopes) { @@ -119,14 +120,14 @@ namespace smt { theory::pop_scope_eh(num_scopes); reset_queues(); - // restore guards - unsigned new_lim = m_guard_preds_lim.size()-num_scopes; - unsigned start = m_guard_preds_lim[new_lim]; - for (unsigned i = start; i < m_guard_preds.size(); ++i) { - m_guards[m_guard_preds.get(i)].pop_back(); + // restore depth book-keeping + unsigned new_lim = m_preds_lim.size()-num_scopes; + unsigned start = m_preds_lim[new_lim]; + for (unsigned i = start; i < m_preds.size(); ++i) { + m_pred_depth.remove(m_preds.get(i)); } - m_guard_preds.resize(start); - m_guard_preds_lim.shrink(new_lim); + m_preds.resize(start); + m_preds_lim.shrink(new_lim); } void theory_recfun::restart_eh() { @@ -169,24 +170,49 @@ namespace smt { m_q_body_expand.clear(); } - void theory_recfun::max_depth_limit(ptr_vector const& guards) { + /** + * make clause `depth_limit => ~guard` + * the guard appears at a depth below the current cutoff. + */ + void theory_recfun::assert_max_depth_limit(expr* guard) { TRACEFN("max-depth limit"); literal_vector c; - // make clause `depth_limit => V_{g : guards of non-recursive cases} g` - - // first literal must be the depth limit one app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); c.push_back(~mk_literal(dlimit)); - enable_trace("recfun"); - TRACE("recfun", ctx().display(tout << c.back() << " " << dlimit << "\n");); - - for (expr * g : guards) { - c.push_back(mk_literal(g)); - } + c.push_back(~mk_literal(guard)); TRACEFN("max-depth limit: add clause " << pp_lits(ctx(), c)); m_q_clauses.push_back(std::move(c)); } + /** + * retrieve depth associated with predicate or expression. + */ + unsigned theory_recfun::get_depth(expr* e) { + unsigned d = 0; + m_pred_depth.find(e, d); + return d; + } + + /** + * Update depth of subterms of e with respect to d. + */ + void theory_recfun::set_depth(unsigned d, expr* e) { + struct insert_c { + theory_recfun& th; + unsigned m_depth; + insert_c(theory_recfun& th, unsigned d): th(th), m_depth(d) {} + void operator()(app* e) { + if ((th.u().is_defined(e) || th.u().is_case_pred(e)) && !th.m_pred_depth.contains(e)) { + th.m_pred_depth.insert(e, m_depth); + } + } + void operator()(quantifier*) {} + void operator()(var*) {} + }; + insert_c proc(*this, d); + for_each_expr(proc, e); + } + /** * if `is_true` and `v = C_f_i(t1...tn)`, * then body-expand i-th case of `f(t1...tn)` @@ -203,6 +229,7 @@ namespace smt { // replace `vars` by `args` in `e` expr_ref theory_recfun::apply_args( + unsigned depth, recfun::vars const & vars, ptr_vector const & args, expr * e) { @@ -250,7 +277,8 @@ namespace smt { SASSERT(e.m_def->is_fun_macro()); auto & vars = e.m_def->get_vars(); expr_ref lhs(e.m_lhs, m); - expr_ref rhs(apply_args(vars, e.m_args, e.m_def->get_macro_rhs()), m); + unsigned depth = get_depth(e.m_lhs); + expr_ref rhs(apply_args(depth, vars, e.m_args, e.m_def->get_macro_rhs()), m); literal lit = mk_eq_lit(lhs, rhs); ctx().mk_th_axiom(get_id(), 1, &lit); TRACEFN("macro expansion yields " << mk_pp(rhs, m) << "\n" << @@ -273,13 +301,21 @@ namespace smt { for (recfun::case_def const & c : e.m_def->get_cases()) { // applied predicate to `args` app_ref pred_applied = c.apply_case_predicate(e.m_args); + + // cut off cases below max-depth + unsigned depth = get_depth(pred_applied); + if (depth >= get_max_depth()) { + assert_max_depth_limit(pred_applied); + continue; + } + SASSERT(u().owns_app(pred_applied)); literal concl = mk_literal(pred_applied); literal_vector guards; guards.push_back(concl); for (auto & g : c.get_guards()) { - expr_ref ga = apply_args(vars, e.m_args, g); + expr_ref ga = apply_args(depth, vars, e.m_args, g); literal guard = mk_literal(ga); guards.push_back(~guard); literal c[2] = {~concl, guard}; @@ -288,18 +324,8 @@ namespace smt { ctx().mk_th_axiom(get_id(), guards); if (c.is_immediate()) { - body_expansion be(c, e.m_args); + body_expansion be(e.m_lhs, c, e.m_args); assert_body_axiom(be); - - // add to set of local assumptions, for depth-limit purpose - - // func_decl* d = pred_applied->get_decl(); - m_guard_preds.push_back(pred_applied); - auto& vec = m_guards.insert_if_not_there2(e.m_lhs, ptr_vector())->get_data().m_value; - vec.push_back(pred_applied); - if (vec.size() == get_max_depth()) { - max_depth_limit(vec); - } } } } @@ -317,12 +343,13 @@ namespace smt { auto & vars = d.get_vars(); auto & args = e.m_args; SASSERT(is_standard_order(vars)); + unsigned depth = get_depth(e.m_lhs); expr_ref lhs(u().mk_fun_defined(d, args), m); - expr_ref rhs = apply_args(vars, args, e.m_cdef->get_rhs()); + expr_ref rhs = apply_args(depth, vars, args, e.m_cdef->get_rhs()); literal_vector clause; for (auto & g : e.m_cdef->get_guards()) { - expr_ref guard = apply_args(vars, args, g); + expr_ref guard = apply_args(depth, vars, args, g); clause.push_back(~mk_literal(guard)); if (clause.back() == true_literal) { return; diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 68e2609d7..240e595c3 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -35,11 +35,11 @@ namespace smt { // one case-expansion of `f(t1...tn)` struct case_expansion { - expr * m_lhs; // the term to expand + app * m_lhs; // the term to expand recfun_def * m_def; ptr_vector m_args; - case_expansion(recfun_util& u, app * n) : m_lhs(n), m_def(0), m_args() - { + case_expansion(recfun_util& u, app * n) : + m_lhs(n), m_def(nullptr), m_args() { SASSERT(u.is_defined(n)); func_decl * d = n->get_decl(); const symbol& name = d->get_name(); @@ -66,16 +66,20 @@ namespace smt { // one body-expansion of `f(t1...tn)` using a `C_f_i(t1...tn)` struct body_expansion { + app* m_lhs; recfun_case_def const * m_cdef; ptr_vector m_args; - body_expansion(recfun_util& u, app * n) : m_cdef(0), m_args() { + body_expansion(recfun_util& u, app * n) : m_lhs(n), m_cdef(0), m_args() { m_cdef = &u.get_case_def(n); m_args.append(n->get_num_args(), n->get_args()); } - body_expansion(recfun_case_def const & d, ptr_vector & args) : m_cdef(&d), m_args(args) {} - body_expansion(body_expansion const & from): m_cdef(from.m_cdef), m_args(from.m_args) {} - body_expansion(body_expansion && from) : m_cdef(from.m_cdef), m_args(std::move(from.m_args)) {} + body_expansion(app* lhs, recfun_case_def const & d, ptr_vector & args) : + m_lhs(lhs), m_cdef(&d), m_args(args) {} + body_expansion(body_expansion const & from): + m_lhs(from.m_lhs), m_cdef(from.m_cdef), m_args(from.m_args) {} + body_expansion(body_expansion && from) : + m_lhs(from.m_lhs), m_cdef(from.m_cdef), m_args(std::move(from.m_args)) {} }; struct pp_body_expansion { @@ -86,14 +90,16 @@ namespace smt { friend std::ostream& operator<<(std::ostream&, pp_body_expansion const &); - ast_manager& m; - recfun_decl_plugin& m_plugin; - recfun_util& m_util; - stats m_stats; - obj_map > m_guards; - app_ref_vector m_guard_preds; - unsigned_vector m_guard_preds_lim; - unsigned m_max_depth; // for fairness and termination + ast_manager& m; + recfun_decl_plugin& m_plugin; + recfun_util& m_util; + stats m_stats; + + // book-keeping for depth of predicates + obj_map m_pred_depth; + expr_ref_vector m_preds; + unsigned_vector m_preds_lim; + unsigned m_max_depth; // for fairness and termination vector m_q_case_expand; vector m_q_body_expand; @@ -107,14 +113,20 @@ namespace smt { bool is_case_pred(enode * e) const { return is_case_pred(e->get_owner()); } void reset_queues(); - expr_ref apply_args(recfun::vars const & vars, ptr_vector const & args, expr * e); //!< substitute variables by args + expr_ref apply_args(unsigned depth, recfun::vars const & vars, ptr_vector const & args, expr * e); //!< substitute variables by args void assert_macro_axiom(case_expansion & e); void assert_case_axioms(case_expansion & e); void assert_body_axiom(body_expansion & e); literal mk_literal(expr* e); - void max_depth_limit(ptr_vector const& guards); + + void assert_max_depth_limit(expr* guard); + unsigned get_depth(expr* e); + void set_depth(unsigned d, expr* e); + literal mk_eq_lit(expr* l, expr* r); - bool is_standard_order(recfun::vars const& vars) const { return vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0; } + bool is_standard_order(recfun::vars const& vars) const { + return vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0; + } protected: void push_case_expand(case_expansion&& e) { m_q_case_expand.push_back(e); } void push_body_expand(body_expansion&& e) { m_q_body_expand.push_back(e); } From 66f2a7636bce13b42a2073b2fc344ac34c4c6872 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 22 Oct 2018 04:59:51 -0700 Subject: [PATCH 53/65] depth Signed-off-by: Nikolaj Bjorner --- src/smt/params/smt_params_helper.pyg | 3 +-- src/smt/theory_recfun.cpp | 26 ++++++++++---------------- src/smt/theory_recfun.h | 4 +--- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 37ed061b4..1c96826d6 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -96,6 +96,5 @@ def_module_params(module_name='smt', ('core.extend_nonlocal_patterns', BOOL, False, 'extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier\'s body'), ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), ('dt_lazy_splits', UINT, 1, 'How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy'), - ('recfun.native', BOOL, False, 'use native rec-fun solver'), - ('recfun.max_depth', UINT, 2, 'maximum depth of unrolling for recursive functions') + ('recfun.native', BOOL, False, 'use native rec-fun solver') )) diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index bd6f7c3fa..e642dfd85 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -34,7 +34,7 @@ namespace smt { m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), m_preds(m), - m_max_depth(UINT_MAX), + m_max_depth(2), m_q_case_expand(), m_q_body_expand() { @@ -46,14 +46,6 @@ namespace smt { char const * theory_recfun::get_name() const { return "recfun"; } - unsigned theory_recfun::get_max_depth() { - if (m_max_depth == UINT_MAX) { - smt_params_helper p(ctx().get_params()); - set_max_depth(p.recfun_max_depth()); - } - return m_max_depth; - } - theory* theory_recfun::mk_fresh(context* new_ctx) { return alloc(theory_recfun, new_ctx->get_manager()); } @@ -177,7 +169,7 @@ namespace smt { void theory_recfun::assert_max_depth_limit(expr* guard) { TRACEFN("max-depth limit"); literal_vector c; - app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); + app_ref dlimit = m_util.mk_depth_limit_pred(m_max_depth); c.push_back(~mk_literal(dlimit)); c.push_back(~mk_literal(guard)); TRACEFN("max-depth limit: add clause " << pp_lits(ctx(), c)); @@ -202,8 +194,10 @@ namespace smt { unsigned m_depth; insert_c(theory_recfun& th, unsigned d): th(th), m_depth(d) {} void operator()(app* e) { - if ((th.u().is_defined(e) || th.u().is_case_pred(e)) && !th.m_pred_depth.contains(e)) { + if (th.u().is_defined(e) && !th.m_pred_depth.contains(e)) { th.m_pred_depth.insert(e, m_depth); + th.m_preds.push_back(e); + TRACEFN("depth " << m_depth << " : " << mk_pp(e, th.m)); } } void operator()(quantifier*) {} @@ -238,6 +232,7 @@ namespace smt { expr_ref new_body(m); new_body = subst(e, args.size(), args.c_ptr()); ctx().get_rewriter()(new_body); // simplify + set_depth(depth + 1, new_body); return new_body; } @@ -303,8 +298,8 @@ namespace smt { app_ref pred_applied = c.apply_case_predicate(e.m_args); // cut off cases below max-depth - unsigned depth = get_depth(pred_applied); - if (depth >= get_max_depth()) { + unsigned depth = get_depth(e.m_lhs); + if (depth >= m_max_depth) { assert_max_depth_limit(pred_applied); continue; } @@ -375,7 +370,7 @@ namespace smt { void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { if (u().has_def()) { - app_ref dlimit = m_util.mk_depth_limit_pred(get_max_depth()); + app_ref dlimit = m_util.mk_depth_limit_pred(m_max_depth); TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m)); assumptions.push_back(dlimit); } @@ -385,8 +380,7 @@ namespace smt { bool theory_recfun::should_research(expr_ref_vector & unsat_core) { for (auto & e : unsat_core) { if (u().is_depth_limit(e)) { - unsigned new_depth = (3 * (1 + get_max_depth())) / 2; - set_max_depth(new_depth); + m_max_depth = (3 * m_max_depth) / 2; return true; } } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 240e595c3..af12853a1 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -149,13 +149,11 @@ namespace smt { void new_diseq_eh(theory_var v1, theory_var v2) override {} void add_theory_assumptions(expr_ref_vector & assumptions) override; - void set_max_depth(unsigned n) { SASSERT(n>0); m_max_depth = n; } - unsigned get_max_depth(); - public: theory_recfun(ast_manager & m); ~theory_recfun() override; theory * mk_fresh(context * new_ctx) override; + void init_search_eh() override { m_max_depth = 2; } void display(std::ostream & out) const override; void collect_statistics(::statistics & st) const override; }; From 5c80b142c5b36a72722108fa241b4b251237fa63 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 22 Oct 2018 07:22:58 -0700 Subject: [PATCH 54/65] fix build Signed-off-by: Nikolaj Bjorner --- src/smt/params/smt_params.cpp | 1 - src/smt/params/smt_params.h | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index 3f800455b..650afda25 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -27,7 +27,6 @@ void smt_params::updt_local_params(params_ref const & _p) { m_random_seed = p.random_seed(); m_relevancy_lvl = p.relevancy(); m_ematching = p.ematching(); - m_recfun_max_depth = p.recfun_max_depth(); m_phase_selection = static_cast(p.phase_selection()); m_restart_strategy = static_cast(p.restart_strategy()); m_restart_factor = p.restart_factor(); diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index a5923089c..8901697b7 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -108,9 +108,6 @@ struct smt_params : public preprocessor_params, bool m_new_core2th_eq; bool m_ematching; - // TODO: move into its own file? - unsigned m_recfun_max_depth; - // ----------------------------------- // // Case split strategy @@ -264,7 +261,6 @@ struct smt_params : public preprocessor_params, m_display_features(false), m_new_core2th_eq(true), m_ematching(true), - m_recfun_max_depth(50), m_case_split_strategy(CS_ACTIVITY_DELAY_NEW), m_rel_case_split_order(0), m_lookahead_diseq(false), From aa6e1badf228405e2a574c7dc27f0ef90d302dfd Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 23 Oct 2018 08:16:26 -0700 Subject: [PATCH 55/65] recfun Signed-off-by: Nikolaj Bjorner --- src/ast/array_decl_plugin.cpp | 17 +++++++--- src/ast/ast.cpp | 18 +++++++++-- src/ast/ast.h | 13 ++++++++ src/parsers/smt2/smt2parser.cpp | 12 ++++--- src/smt/params/smt_params_helper.pyg | 3 +- src/smt/smt_context.cpp | 5 ++- src/smt/smt_context_inv.cpp | 2 +- src/smt/theory_recfun.cpp | 48 ++++++++++++++++++++-------- src/smt/theory_recfun.h | 10 +++--- 9 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/ast/array_decl_plugin.cpp b/src/ast/array_decl_plugin.cpp index 868b35d00..fa17b591d 100644 --- a/src/ast/array_decl_plugin.cpp +++ b/src/ast/array_decl_plugin.cpp @@ -253,7 +253,10 @@ func_decl* array_decl_plugin::mk_select(unsigned arity, sort * const * domain) { if (!parameters[i].is_ast() || !is_sort(parameters[i].get_ast()) || !m_manager->compatible_sorts(domain[i+1], to_sort(parameters[i].get_ast()))) { - m_manager->raise_exception("domain sort and parameter do not match"); + std::stringstream strm; + strm << "domain sort " << sort_ref(domain[i+1], *m_manager) << " and parameter "; + strm << parameter_pp(parameters[i], *m_manager) << " do not match"; + m_manager->raise_exception(strm.str().c_str()); UNREACHABLE(); return nullptr; } @@ -292,8 +295,12 @@ func_decl * array_decl_plugin::mk_store(unsigned arity, sort * const * domain) { m_manager->raise_exception("expecting sort parameter"); return nullptr; } - if (!m_manager->compatible_sorts(to_sort(parameters[i].get_ast()), domain[i+1])) { - m_manager->raise_exception("domain sort and parameter do not match"); + sort* srt1 = to_sort(parameters[i].get_ast()); + sort* srt2 = domain[i+1]; + if (!m_manager->compatible_sorts(srt1, srt2)) { + std::stringstream strm; + strm << "domain sort " << sort_ref(srt2, *m_manager) << " and parameter sort " << sort_ref(srt2, *m_manager) << " do not match"; + m_manager->raise_exception(strm.str()); UNREACHABLE(); return nullptr; } @@ -326,13 +333,13 @@ bool array_decl_plugin::check_set_arguments(unsigned arity, sort * const * domai if (domain[i] != domain[0]) { std::ostringstream buffer; buffer << "arguments " << 1 << " and " << (i+1) << " have different sorts"; - m_manager->raise_exception(buffer.str().c_str()); + m_manager->raise_exception(buffer.str()); return false; } if (domain[i]->get_family_id() != m_family_id) { std::ostringstream buffer; buffer << "argument " << (i+1) << " is not of array sort"; - m_manager->raise_exception(buffer.str().c_str()); + m_manager->raise_exception(buffer.str()); return false; } } diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 8750425a8..5f36a61ef 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -16,8 +16,8 @@ Author: Revision History: --*/ -#include -#include +#include +#include #include "ast/ast.h" #include "ast/ast_pp.h" #include "ast/ast_ll_pp.h" @@ -1541,6 +1541,20 @@ void ast_manager::raise_exception(char const * msg) { throw ast_exception(msg); } +void ast_manager::raise_exception(std::string const& msg) { + throw ast_exception(msg.c_str()); +} + +std::ostream& ast_manager::display(std::ostream& out, parameter const& p) { + switch (p.get_kind()) { + case parameter::PARAM_AST: + return out << ast_ref(p.get_ast(), *this); + default: + return p.display(out); + } + return out; +} + void ast_manager::copy_families_plugins(ast_manager const & from) { TRACE("copy_families_plugins", diff --git a/src/ast/ast.h b/src/ast/ast.h index acef0c659..37a6126f2 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1562,6 +1562,9 @@ public: // Equivalent to throw ast_exception(msg) Z3_NORETURN void raise_exception(char const * msg); + Z3_NORETURN void raise_exception(std::string const& s); + + std::ostream& display(std::ostream& out, parameter const& p); bool is_format_manager() const { return m_format_manager == nullptr; } @@ -2498,6 +2501,16 @@ public: void mark(ast * n, bool flag) { if (flag) mark(n); else reset_mark(n); } }; +struct parameter_pp { + parameter const& p; + ast_manager& m; + parameter_pp(parameter const& p, ast_manager& m): p(p), m(m) {} +}; + +inline std::ostream& operator<<(std::ostream& out, parameter_pp const& pp) { + return pp.m.display(out, pp.p); +} + typedef ast_ref_fast_mark<1> ast_ref_fast_mark1; typedef ast_ref_fast_mark<2> ast_ref_fast_mark2; typedef ast_ref_fast_mark1 expr_ref_fast_mark1; diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 7ed90d1c8..106a77c0e 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -2277,10 +2277,14 @@ namespace smt2 { parse_expr(); if (m().get_sort(expr_stack().back()) != sort_stack().back()) throw parser_exception("invalid function/constant definition, sort mismatch"); - if (is_fun) - m_ctx.insert(id, num_vars, sort_stack().c_ptr() + sort_spos, expr_stack().back()); - else - m_ctx.model_add(id, num_vars, sort_stack().c_ptr() + sort_spos, expr_stack().back()); + sort* const* sorts = sort_stack().c_ptr() + sort_spos; + expr* t = expr_stack().back(); + if (is_fun) { + m_ctx.insert(id, num_vars, sorts, t); + } + else { + m_ctx.model_add(id, num_vars, sorts, t); + } check_rparen("invalid function/constant definition, ')' expected"); // restore stacks & env symbol_stack().shrink(sym_spos); diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 1c96826d6..2f87f24c0 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -96,5 +96,6 @@ def_module_params(module_name='smt', ('core.extend_nonlocal_patterns', BOOL, False, 'extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier\'s body'), ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), ('dt_lazy_splits', UINT, 1, 'How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy'), - ('recfun.native', BOOL, False, 'use native rec-fun solver') + ('recfun.native', BOOL, False, 'use native rec-fun solver'), + ('recfun.depth', UINT, 2, 'initial depth for maxrec expansion') )) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index ab8707e73..a189c6be0 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -1384,7 +1384,10 @@ namespace smt { SASSERT(m_manager.is_eq(n)); expr * lhs = n->get_arg(0); expr * rhs = n->get_arg(1); - if (val == l_true) { + if (m_manager.is_bool(lhs)) { + // no-op + } + else if (val == l_true) { add_eq(get_enode(lhs), get_enode(rhs), eq_justification(l)); } else { diff --git a/src/smt/smt_context_inv.cpp b/src/smt/smt_context_inv.cpp index 7f409622b..a6c4f49b4 100644 --- a/src/smt/smt_context_inv.cpp +++ b/src/smt/smt_context_inv.cpp @@ -303,7 +303,7 @@ namespace smt { for (bool_var v = 0; v < num; v++) { if (has_enode(v)) { enode * n = bool_var2enode(v); - if (n->is_eq() && is_relevant(n) && get_assignment(v) == l_false) { + if (n->is_eq() && is_relevant(n) && get_assignment(v) == l_false && !m_manager.is_iff(n->get_owner())) { TRACE("check_th_diseq_propagation", tout << "checking: #" << n->get_owner_id() << " " << mk_bounded_pp(n->get_owner(), m_manager) << "\n";); enode * lhs = n->get_arg(0)->get_root(); enode * rhs = n->get_arg(1)->get_root(); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index e642dfd85..1fbedc63d 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -50,7 +50,14 @@ namespace smt { return alloc(theory_recfun, new_ctx->get_manager()); } + void theory_recfun::init_search_eh() { + smt_params_helper p(ctx().get_params()); + m_max_depth = p.recfun_depth(); + } + + bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { + TRACEFN(mk_pp(atom, m)); for (expr * arg : *atom) { ctx().internalize(arg, false); } @@ -76,7 +83,13 @@ namespace smt { } void theory_recfun::reset_queues() { + for (auto* e : m_q_case_expand) { + dealloc(e); + } m_q_case_expand.reset(); + for (auto* e : m_q_body_expand) { + dealloc(e); + } m_q_body_expand.reset(); m_q_clauses.clear(); } @@ -96,8 +109,7 @@ namespace smt { SASSERT(ctx().relevancy()); if (u().is_defined(n)) { TRACEFN("relevant_eh: (defined) " << mk_pp(n, m)); - case_expansion e(u(), n); - push_case_expand(std::move(e)); + push_case_expand(alloc(case_expansion, u(), n)); } } @@ -114,11 +126,13 @@ namespace smt { // restore depth book-keeping unsigned new_lim = m_preds_lim.size()-num_scopes; +#if 0 unsigned start = m_preds_lim[new_lim]; for (unsigned i = start; i < m_preds.size(); ++i) { m_pred_depth.remove(m_preds.get(i)); } m_preds.resize(start); +#endif m_preds_lim.shrink(new_lim); } @@ -141,25 +155,31 @@ namespace smt { ctx().mk_th_axiom(get_id(), c); } m_q_clauses.clear(); - + for (unsigned i = 0; i < m_q_case_expand.size(); ++i) { - case_expansion & e = m_q_case_expand[i]; - if (e.m_def->is_fun_macro()) { + case_expansion* e = m_q_case_expand[i]; + if (e->m_def->is_fun_macro()) { // body expand immediately - assert_macro_axiom(e); + assert_macro_axiom(*e); } else { // case expand - SASSERT(e.m_def->is_fun_defined()); - assert_case_axioms(e); + SASSERT(e->m_def->is_fun_defined()); + assert_case_axioms(*e); } + dealloc(e); + m_q_case_expand[i] = nullptr; } - m_q_case_expand.clear(); + m_stats.m_case_expansions += m_q_case_expand.size(); + m_q_case_expand.reset(); for (unsigned i = 0; i < m_q_body_expand.size(); ++i) { - assert_body_axiom(m_q_body_expand[i]); + assert_body_axiom(*m_q_body_expand[i]); + dealloc(m_q_body_expand[i]); + m_q_body_expand[i] = nullptr; } - m_q_body_expand.clear(); + m_stats.m_body_expansions += m_q_body_expand.size(); + m_q_body_expand.reset(); } /** @@ -167,7 +187,6 @@ namespace smt { * the guard appears at a depth below the current cutoff. */ void theory_recfun::assert_max_depth_limit(expr* guard) { - TRACEFN("max-depth limit"); literal_vector c; app_ref dlimit = m_util.mk_depth_limit_pred(m_max_depth); c.push_back(~mk_literal(dlimit)); @@ -216,8 +235,7 @@ namespace smt { if (is_true && u().is_case_pred(e)) { TRACEFN("assign_case_pred_true " << mk_pp(e, m)); // body-expand - body_expansion b_e(u(), to_app(e)); - push_body_expand(std::move(b_e)); + push_body_expand(alloc(body_expansion, u(), to_app(e))); } } @@ -268,6 +286,7 @@ namespace smt { * 2. add unit clause `f(args) = rhs` */ void theory_recfun::assert_macro_axiom(case_expansion & e) { + m_stats.m_macro_expansions++; TRACEFN("case expansion " << pp_case_expansion(e, m) << "\n"); SASSERT(e.m_def->is_fun_macro()); auto & vars = e.m_def->get_vars(); @@ -381,6 +400,7 @@ namespace smt { for (auto & e : unsat_core) { if (u().is_depth_limit(e)) { m_max_depth = (3 * m_max_depth) / 2; + IF_VERBOSE(1, verbose_stream() << "(smt.recfun :increase-depth " << m_max_depth << ")\n"); return true; } } diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index af12853a1..8249e3412 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -101,8 +101,8 @@ namespace smt { unsigned_vector m_preds_lim; unsigned m_max_depth; // for fairness and termination - vector m_q_case_expand; - vector m_q_body_expand; + ptr_vector m_q_case_expand; + ptr_vector m_q_body_expand; vector m_q_clauses; recfun_util & u() const { return m_util; } @@ -128,8 +128,8 @@ namespace smt { return vars.size() == 0 || vars[vars.size()-1]->get_idx() == 0; } protected: - void push_case_expand(case_expansion&& e) { m_q_case_expand.push_back(e); } - void push_body_expand(body_expansion&& e) { m_q_body_expand.push_back(e); } + void push_case_expand(case_expansion* e) { m_q_case_expand.push_back(e); } + void push_body_expand(body_expansion* e) { m_q_body_expand.push_back(e); } bool internalize_atom(app * atom, bool gate_ctx) override; bool internalize_term(app * term) override; @@ -153,7 +153,7 @@ namespace smt { theory_recfun(ast_manager & m); ~theory_recfun() override; theory * mk_fresh(context * new_ctx) override; - void init_search_eh() override { m_max_depth = 2; } + void init_search_eh() override; void display(std::ostream & out) const override; void collect_statistics(::statistics & st) const override; }; From 67077d960eb3bff9d695a099d22364156a5d24e9 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 23 Oct 2018 14:16:07 -0700 Subject: [PATCH 56/65] working with incremental depth Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 3 +- src/ast/expr_functors.cpp | 6 +- src/ast/expr_functors.h | 4 +- src/ast/recfun_decl_plugin.cpp | 45 ++++----------- src/ast/recfun_decl_plugin.h | 36 ++++++------ src/cmd_context/cmd_context.cpp | 98 +++++++++++++++++---------------- src/smt/theory_recfun.cpp | 47 ++++++++++------ src/smt/theory_recfun.h | 14 +++-- 8 files changed, 127 insertions(+), 126 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 5f36a61ef..e2ff19776 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1548,7 +1548,8 @@ void ast_manager::raise_exception(std::string const& msg) { std::ostream& ast_manager::display(std::ostream& out, parameter const& p) { switch (p.get_kind()) { case parameter::PARAM_AST: - return out << ast_ref(p.get_ast(), *this); + std::cout << "ast: " << p.get_ast() << "\n"; + return out << mk_pp(p.get_ast(), *this); default: return p.display(out); } diff --git a/src/ast/expr_functors.cpp b/src/ast/expr_functors.cpp index cada71ac9..6a2138106 100644 --- a/src/ast/expr_functors.cpp +++ b/src/ast/expr_functors.cpp @@ -69,7 +69,11 @@ void check_pred::visit(expr* e) { case AST_QUANTIFIER: { quantifier* q = to_quantifier(e); expr* arg = q->get_expr(); - if (m_visited.is_marked(arg)) { + if (!m_check_quantifiers) { + todo.pop_back(); + m_visited.mark(e, true); + } + else if (m_visited.is_marked(arg)) { todo.pop_back(); if (m_pred_holds.is_marked(arg)) { m_pred_holds.mark(e, true); diff --git a/src/ast/expr_functors.h b/src/ast/expr_functors.h index 5a4cac477..5825bea73 100644 --- a/src/ast/expr_functors.h +++ b/src/ast/expr_functors.h @@ -53,8 +53,10 @@ class check_pred { ast_mark m_pred_holds; ast_mark m_visited; expr_ref_vector m_refs; + bool m_check_quantifiers; public: - check_pred(i_expr_pred& p, ast_manager& m) : m_pred(p), m_refs(m) {} + check_pred(i_expr_pred& p, ast_manager& m, bool check_quantifiers = true) : + m_pred(p), m_refs(m), m_check_quantifiers(check_quantifiers) {} bool operator()(expr* e); diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index e5796b119..d1b9a861e 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -1,4 +1,6 @@ /*++ +Copyright (c) 2017 Microsoft Corporation, Simon Cruanes + Module Name: recfun_decl_plugin.cpp @@ -68,8 +70,11 @@ namespace recfun { ite_find_p(ast_manager & m) : m(m) {} virtual bool operator()(expr * e) { return m.is_ite(e); } }; + // ignore ites under quantifiers. + // this is redundant as the code + // that unfolds ites uses quantifier-free portion. ite_find_p p(m); - check_pred cp(p, m); + check_pred cp(p, m, false); return cp(e); } @@ -249,7 +254,9 @@ namespace recfun { else if (is_app(e)) { // explore arguments for (expr * arg : *to_app(e)) { - stack.push_back(arg); + if (contains_ite(arg)) { + stack.push_back(arg); + } } } } @@ -298,7 +305,7 @@ namespace recfun { } } - TRACEFN("done analysing " << get_name()); + TRACEFN("done analyzing " << get_name()); } /* @@ -405,42 +412,12 @@ namespace recfun { return d.get_def(); } - func_decl * plugin::mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range) - { - VALIDATE_PARAM(m(), m().is_bool(range) && num_parameters == 1 && parameters[0].is_ast()); - func_decl_info info(m_family_id, OP_FUN_CASE_PRED, num_parameters, parameters); - info.m_private_parameters = true; - return m().mk_func_decl(symbol(parameters[0].get_symbol()), arity, domain, range, info); - } - - func_decl * plugin::mk_fun_defined_decl(decl_kind k, unsigned num_parameters, - parameter const * parameters, - unsigned arity, sort * const * domain, sort * range) - { - VALIDATE_PARAM(m(), num_parameters == 1 && parameters[0].is_ast()); - func_decl_info info(m_family_id, k, num_parameters, parameters); - info.m_private_parameters = true; - return m().mk_func_decl(symbol(parameters[0].get_symbol()), arity, - domain, range, info); - } - // generic declaration of symbols func_decl * plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range) { UNREACHABLE(); - // TBD: parameter usage seems inconsistent with other uses. - IF_VERBOSE(0, verbose_stream() << "mk-func-decl " << k << "\n"); - switch (k) { - case OP_FUN_CASE_PRED: - return mk_fun_pred_decl(num_parameters, parameters, arity, domain, range); - case OP_FUN_DEFINED: - return mk_fun_defined_decl(k, num_parameters, parameters, arity, domain, range); - default: - UNREACHABLE(); - return nullptr; - } + return nullptr; } } } diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 1e6f873e2..cae83584a 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -1,4 +1,6 @@ /*++ +Copyright (c) 2017 Microsoft Corporation, Simon Cruanes + Module Name: recfun_decl_plugin.h @@ -149,40 +151,36 @@ namespace recfun { ast_manager & m() { return *m_manager; } public: plugin(); - virtual ~plugin() override; - virtual void finalize() override; + ~plugin() override; + void finalize() override; util & u() const; // build or return util - virtual bool is_fully_interp(sort * s) const override { return false; } // might depend on unin sorts + bool is_fully_interp(sort * s) const override { return false; } // might depend on unin sorts - virtual decl_plugin * mk_fresh() override { return alloc(plugin); } + decl_plugin * mk_fresh() override { return alloc(plugin); } - virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override { UNREACHABLE(); return 0; } + sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override { + UNREACHABLE(); return nullptr; + } - virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range) override; - + func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) override; + promise_def mk_def(symbol const& name, unsigned n, sort *const * params, sort * range); - + void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); - + def* mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs); bool has_def(const symbol& s) const { return m_defs.contains(s); } - bool has_def() const { return !m_defs.empty(); } + bool has_defs() const { return !m_defs.empty(); } def const& get_def(const symbol& s) const { return *(m_defs[s]); } promise_def get_promise_def(const symbol &s) const { return promise_def(&u(), m_defs[s]); } def& get_def(symbol const& s) { return *(m_defs[s]); } bool has_case_def(func_decl* f) const { return m_case_defs.contains(f); } case_def& get_case_def(func_decl* f) { SASSERT(has_case_def(f)); return *(m_case_defs[f]); } bool is_declared(symbol const& s) const { return m_defs.contains(s); } - private: - func_decl * mk_fun_pred_decl(unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range); - func_decl * mk_fun_defined_decl(decl_kind k, - unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range); }; } @@ -200,7 +198,7 @@ namespace recfun { public: util(ast_manager &m, family_id); - virtual ~util(); + ~util(); ast_manager & m() { return m_manager; } th_rewriter & get_th_rewriter() { return m_th_rw; } @@ -209,7 +207,7 @@ namespace recfun { bool is_depth_limit(expr * e) const { return is_app_of(e, m_fid, OP_DEPTH_LIMIT); } bool owns_app(app * e) const { return e->get_family_id() == m_fid; } - bool has_def() const { return m_plugin->has_def(); } + bool has_defs() const { return m_plugin->has_defs(); } //::entry * e = m_func_decls.insert_if_not_there2(s, func_decls()); func_decls & fs = e->get_data().m_value; if (!fs.insert(m(), f)) { @@ -834,9 +836,11 @@ void cmd_context::insert(symbol const & s, psort_decl * p) { void cmd_context::insert(symbol const & s, unsigned arity, sort *const* domain, expr * t) { expr_ref _t(t, m()); +#if 0 if (m_builtin_decls.contains(s)) { throw cmd_exception("invalid macro/named expression, builtin symbol ", s); } +#endif if (contains_macro(s, arity, domain)) { throw cmd_exception("named expression already defined"); } @@ -967,6 +971,15 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s } func_decl * cmd_context::find_func_decl(symbol const & s) const { + if (contains_macro(s)) { + throw cmd_exception("invalid function declaration reference, named expressions (aka macros) cannot be referenced ", s); + } + func_decls fs; + if (m_func_decls.find(s, fs)) { + if (fs.more_than_one()) + throw cmd_exception("ambiguous function declaration reference, provide full signature to disumbiguate ( (*) ) ", s); + return fs.first(); + } builtin_decl d; if (m_builtin_decls.find(s, d)) { try { @@ -980,15 +993,6 @@ func_decl * cmd_context::find_func_decl(symbol const & s) const { } throw cmd_exception("invalid function declaration reference, must provide signature for builtin symbol ", s); } - if (contains_macro(s)) { - throw cmd_exception("invalid function declaration reference, named expressions (aka macros) cannot be referenced ", s); - } - func_decls fs; - if (m_func_decls.find(s, fs)) { - if (fs.more_than_one()) - throw cmd_exception("ambiguous function declaration reference, provide full signature to disumbiguate ( (*) ) ", s); - return fs.first(); - } throw cmd_exception("invalid function declaration reference, unknown function ", s); return nullptr; } @@ -1013,6 +1017,18 @@ static builtin_decl const & peek_builtin_decl(builtin_decl const & first, family func_decl * cmd_context::find_func_decl(symbol const & s, unsigned num_indices, unsigned const * indices, unsigned arity, sort * const * domain, sort * range) const { + + if (domain && contains_macro(s, arity, domain)) + throw cmd_exception("invalid function declaration reference, named expressions (aka macros) cannot be referenced ", s); + + func_decl * f = nullptr; + func_decls fs; + if (num_indices == 0 && m_func_decls.find(s, fs)) { + f = fs.find(arity, domain, range); + } + if (f) { + return f; + } builtin_decl d; if (domain && m_builtin_decls.find(s, d)) { family_id fid = d.m_fid; @@ -1037,21 +1053,7 @@ func_decl * cmd_context::find_func_decl(symbol const & s, unsigned num_indices, throw cmd_exception("invalid function declaration reference, invalid builtin reference ", s); return f; } - - if (domain && contains_macro(s, arity, domain)) - throw cmd_exception("invalid function declaration reference, named expressions (aka macros) cannot be referenced ", s); - - if (num_indices > 0) - throw cmd_exception("invalid indexed function declaration reference, unknown builtin function ", s); - - func_decl * f = nullptr; - func_decls fs; - if (m_func_decls.find(s, fs)) { - f = fs.find(arity, domain, range); - } - if (f == nullptr) - throw cmd_exception("invalid function declaration reference, unknown function ", s); - return f; + throw cmd_exception("invalid function declaration reference, unknown function ", s); } psort_decl * cmd_context::find_psort_decl(symbol const & s) const { @@ -1088,29 +1090,6 @@ void cmd_context::mk_const(symbol const & s, expr_ref & result) const { void cmd_context::mk_app(symbol const & s, unsigned num_args, expr * const * args, unsigned num_indices, parameter const * indices, sort * range, expr_ref & result) const { - builtin_decl d; - if (m_builtin_decls.find(s, d)) { - family_id fid = d.m_fid; - decl_kind k = d.m_decl; - // Hack: if d.m_next != 0, we use the sort of args[0] (if available) to decide which plugin we use. - if (d.m_decl != 0 && num_args > 0) { - builtin_decl const & d2 = peek_builtin_decl(d, m().get_sort(args[0])->get_family_id()); - fid = d2.m_fid; - k = d2.m_decl; - } - if (num_indices == 0) { - result = m().mk_app(fid, k, 0, nullptr, num_args, args, range); - } - else { - result = m().mk_app(fid, k, num_indices, indices, num_args, args, range); - } - if (result.get() == nullptr) - throw cmd_exception("invalid builtin application ", s); - CHECK_SORT(result.get()); - return; - } - if (num_indices > 0) - throw cmd_exception("invalid use of indexed identifier, unknown builtin function ", s); expr* _t; if (macros_find(s, num_args, args, _t)) { TRACE("macro_bug", tout << "well_sorted_check_enabled(): " << well_sorted_check_enabled() << "\n"; @@ -1126,6 +1105,31 @@ void cmd_context::mk_app(symbol const & s, unsigned num_args, expr * const * arg func_decls fs; if (!m_func_decls.find(s, fs)) { + + builtin_decl d; + if (m_builtin_decls.find(s, d)) { + family_id fid = d.m_fid; + decl_kind k = d.m_decl; + // Hack: if d.m_next != 0, we use the sort of args[0] (if available) to decide which plugin we use. + if (d.m_decl != 0 && num_args > 0) { + builtin_decl const & d2 = peek_builtin_decl(d, m().get_sort(args[0])->get_family_id()); + fid = d2.m_fid; + k = d2.m_decl; + } + if (num_indices == 0) { + result = m().mk_app(fid, k, 0, nullptr, num_args, args, range); + } + else { + result = m().mk_app(fid, k, num_indices, indices, num_args, args, range); + } + if (result.get() == nullptr) + throw cmd_exception("invalid builtin application ", s); + CHECK_SORT(result.get()); + return; + } + if (num_indices > 0) + throw cmd_exception("invalid use of indexed identifier, unknown builtin function ", s); + if (num_args == 0) { throw cmd_exception("unknown constant ", s); } diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 1fbedc63d..e1e741d3d 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -34,7 +34,7 @@ namespace smt { m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), m_preds(m), - m_max_depth(2), + m_max_depth(0), m_q_case_expand(), m_q_body_expand() { @@ -50,11 +50,15 @@ namespace smt { return alloc(theory_recfun, new_ctx->get_manager()); } - void theory_recfun::init_search_eh() { - smt_params_helper p(ctx().get_params()); + void theory_recfun::init(context* ctx) { + theory::init(ctx); + smt_params_helper p(ctx->get_params()); m_max_depth = p.recfun_depth(); + if (m_max_depth < 2) m_max_depth = 2; } + void theory_recfun::init_search_eh() { + } bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { TRACEFN(mk_pp(atom, m)); @@ -199,26 +203,22 @@ namespace smt { * retrieve depth associated with predicate or expression. */ unsigned theory_recfun::get_depth(expr* e) { + SASSERT(u().is_defined(e) || u().is_case_pred(e)); unsigned d = 0; m_pred_depth.find(e, d); + TRACEFN("depth " << d << " " << mk_pp(e, m)); return d; } /** * Update depth of subterms of e with respect to d. */ - void theory_recfun::set_depth(unsigned d, expr* e) { + void theory_recfun::set_depth_rec(unsigned d, expr* e) { struct insert_c { theory_recfun& th; unsigned m_depth; insert_c(theory_recfun& th, unsigned d): th(th), m_depth(d) {} - void operator()(app* e) { - if (th.u().is_defined(e) && !th.m_pred_depth.contains(e)) { - th.m_pred_depth.insert(e, m_depth); - th.m_preds.push_back(e); - TRACEFN("depth " << m_depth << " : " << mk_pp(e, th.m)); - } - } + void operator()(app* e) { th.set_depth(m_depth, e); } void operator()(quantifier*) {} void operator()(var*) {} }; @@ -226,6 +226,14 @@ namespace smt { for_each_expr(proc, e); } + void theory_recfun::set_depth(unsigned depth, expr* e) { + if ((u().is_defined(e) || u().is_case_pred(e)) && !m_pred_depth.contains(e)) { + m_pred_depth.insert(e, depth); + m_preds.push_back(e); + TRACEFN("depth " << depth << " : " << mk_pp(e, m)); + } + } + /** * if `is_true` and `v = C_f_i(t1...tn)`, * then body-expand i-th case of `f(t1...tn)` @@ -250,7 +258,7 @@ namespace smt { expr_ref new_body(m); new_body = subst(e, args.size(), args.c_ptr()); ctx().get_rewriter()(new_body); // simplify - set_depth(depth + 1, new_body); + set_depth_rec(depth + 1, new_body); return new_body; } @@ -312,19 +320,23 @@ namespace smt { SASSERT(e.m_def->is_fun_defined()); // add case-axioms for all case-paths auto & vars = e.m_def->get_vars(); + literal_vector preds; for (recfun::case_def const & c : e.m_def->get_cases()) { // applied predicate to `args` app_ref pred_applied = c.apply_case_predicate(e.m_args); // cut off cases below max-depth unsigned depth = get_depth(e.m_lhs); + set_depth(depth, pred_applied); + SASSERT(u().owns_app(pred_applied)); + literal concl = mk_literal(pred_applied); + preds.push_back(concl); + if (depth >= m_max_depth) { assert_max_depth_limit(pred_applied); continue; } - SASSERT(u().owns_app(pred_applied)); - literal concl = mk_literal(pred_applied); literal_vector guards; guards.push_back(concl); @@ -338,10 +350,11 @@ namespace smt { ctx().mk_th_axiom(get_id(), guards); if (c.is_immediate()) { - body_expansion be(e.m_lhs, c, e.m_args); + body_expansion be(pred_applied, c, e.m_args); assert_body_axiom(be); } } + ctx().mk_th_axiom(get_id(), preds); } /** @@ -357,7 +370,7 @@ namespace smt { auto & vars = d.get_vars(); auto & args = e.m_args; SASSERT(is_standard_order(vars)); - unsigned depth = get_depth(e.m_lhs); + unsigned depth = get_depth(e.m_pred); expr_ref lhs(u().mk_fun_defined(d, args), m); expr_ref rhs = apply_args(depth, vars, args, e.m_cdef->get_rhs()); @@ -388,7 +401,7 @@ namespace smt { } void theory_recfun::add_theory_assumptions(expr_ref_vector & assumptions) { - if (u().has_def()) { + if (u().has_defs()) { app_ref dlimit = m_util.mk_depth_limit_pred(m_max_depth); TRACEFN("add_theory_assumption " << mk_pp(dlimit.get(), m)); assumptions.push_back(dlimit); diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 8249e3412..b0d705ddc 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -66,20 +66,20 @@ namespace smt { // one body-expansion of `f(t1...tn)` using a `C_f_i(t1...tn)` struct body_expansion { - app* m_lhs; + app* m_pred; recfun_case_def const * m_cdef; ptr_vector m_args; - body_expansion(recfun_util& u, app * n) : m_lhs(n), m_cdef(0), m_args() { + body_expansion(recfun_util& u, app * n) : m_pred(n), m_cdef(0), m_args() { m_cdef = &u.get_case_def(n); m_args.append(n->get_num_args(), n->get_args()); } - body_expansion(app* lhs, recfun_case_def const & d, ptr_vector & args) : - m_lhs(lhs), m_cdef(&d), m_args(args) {} + body_expansion(app* pred, recfun_case_def const & d, ptr_vector & args) : + m_pred(pred), m_cdef(&d), m_args(args) {} body_expansion(body_expansion const & from): - m_lhs(from.m_lhs), m_cdef(from.m_cdef), m_args(from.m_args) {} + m_pred(from.m_pred), m_cdef(from.m_cdef), m_args(from.m_args) {} body_expansion(body_expansion && from) : - m_lhs(from.m_lhs), m_cdef(from.m_cdef), m_args(std::move(from.m_args)) {} + m_pred(from.m_pred), m_cdef(from.m_cdef), m_args(std::move(from.m_args)) {} }; struct pp_body_expansion { @@ -122,6 +122,7 @@ namespace smt { void assert_max_depth_limit(expr* guard); unsigned get_depth(expr* e); void set_depth(unsigned d, expr* e); + void set_depth_rec(unsigned d, expr* e); literal mk_eq_lit(expr* l, expr* r); bool is_standard_order(recfun::vars const& vars) const { @@ -148,6 +149,7 @@ namespace smt { void new_eq_eh(theory_var v1, theory_var v2) override {} void new_diseq_eh(theory_var v1, theory_var v2) override {} void add_theory_assumptions(expr_ref_vector & assumptions) override; + void init(context* ctx) override; public: theory_recfun(ast_manager & m); From c5cbf985ca245fd0f4af43bc9928115f41ed40a8 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Fri, 26 Oct 2018 10:11:03 -0500 Subject: [PATCH 57/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/recfun_decl_plugin.cpp | 4 ++++ src/ast/recfun_decl_plugin.h | 3 ++- src/cmd_context/cmd_context.cpp | 5 +++-- src/smt/smt_context.cpp | 2 +- src/smt/theory_recfun.cpp | 15 +++++++++++---- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index d1b9a861e..655462a1d 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -404,6 +404,10 @@ namespace recfun { } } + bool plugin::has_defs() const { + return !m_case_defs.empty(); + } + def* plugin::mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs) { SASSERT(! m_defs.contains(name)); diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index cae83584a..d93ad50b8 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -174,7 +174,7 @@ namespace recfun { def* mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs); bool has_def(const symbol& s) const { return m_defs.contains(s); } - bool has_defs() const { return !m_defs.empty(); } + bool has_defs() const; def const& get_def(const symbol& s) const { return *(m_defs[s]); } promise_def get_promise_def(const symbol &s) const { return promise_def(&u(), m_defs[s]); } def& get_def(symbol const& s) { return *(m_defs[s]); } @@ -207,6 +207,7 @@ namespace recfun { bool is_depth_limit(expr * e) const { return is_app_of(e, m_fid, OP_DEPTH_LIMIT); } bool owns_app(app * e) const { return e->get_family_id() == m_fid; } + //has_defs(); } //mk_def(name, arity, domain, range); @@ -950,7 +950,6 @@ void cmd_context::insert_rec_fun_as_axiom(func_decl *f, expr_ref_vector const& b void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, svector const& ids, expr* rhs) { - TRACE("recfun", tout<< "define recfun " << f->get_name() << " = " << mk_pp(rhs, m()) << "\n";); if (gparams::get_value("smt.recfun.native") != "true") { // just use an axiom @@ -958,6 +957,8 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s return; } + TRACE("recfun", tout<< "define recfun " << f->get_name() << " = " << mk_pp(rhs, m()) << "\n";); + recfun_decl_plugin* p = get_recfun_plugin(); var_ref_vector vars(m()); diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index a189c6be0..a56110bc2 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -1773,7 +1773,7 @@ namespace smt { void context::set_conflict(const b_justification & js, literal not_l) { if (!inconsistent()) { - TRACE("set_conflict", display_literal_verbose(tout, not_l); display(tout, js); ); + TRACE("set_conflict", display_literal_verbose(tout, not_l); display(tout << " ", js); ); m_conflict = js; m_not_l = not_l; } diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index e1e741d3d..1c1ee964d 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -62,6 +62,9 @@ namespace smt { bool theory_recfun::internalize_atom(app * atom, bool gate_ctx) { TRACEFN(mk_pp(atom, m)); + if (!u().has_defs()) { + return false; + } for (expr * arg : *atom) { ctx().internalize(arg, false); } @@ -76,6 +79,9 @@ namespace smt { } bool theory_recfun::internalize_term(app * term) { + if (!u().has_defs()) { + return false; + } for (expr* e : *term) { ctx().internalize(e, false); } @@ -111,26 +117,26 @@ namespace smt { */ void theory_recfun::relevant_eh(app * n) { SASSERT(ctx().relevancy()); - if (u().is_defined(n)) { + if (u().is_defined(n) && u().has_defs()) { TRACEFN("relevant_eh: (defined) " << mk_pp(n, m)); push_case_expand(alloc(case_expansion, u(), n)); } } void theory_recfun::push_scope_eh() { - TRACEFN("push_scope"); theory::push_scope_eh(); m_preds_lim.push_back(m_preds.size()); } void theory_recfun::pop_scope_eh(unsigned num_scopes) { - TRACEFN("pop_scope " << num_scopes); theory::pop_scope_eh(num_scopes); reset_queues(); // restore depth book-keeping unsigned new_lim = m_preds_lim.size()-num_scopes; #if 0 + // depth tracking of recursive unfolding is + // turned off when enabling this code: unsigned start = m_preds_lim[new_lim]; for (unsigned i = start; i < m_preds.size(); ++i) { m_pred_depth.remove(m_preds.get(i)); @@ -337,7 +343,6 @@ namespace smt { continue; } - literal_vector guards; guards.push_back(concl); for (auto & g : c.get_guards()) { @@ -354,6 +359,8 @@ namespace smt { assert_body_axiom(be); } } + // the disjunction of branches is asserted + // to close the available cases. ctx().mk_th_axiom(get_id(), preds); } From 51a0022450c7d1c59e211b93dff49b239ff0636a Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 27 Oct 2018 11:41:18 -0500 Subject: [PATCH 58/65] add recfun to API Signed-off-by: Nikolaj Bjorner --- examples/c++/example.cpp | 15 ++++++++++ src/api/api_ast.cpp | 50 ++++++++++++++++++++++++++++++++ src/api/api_context.cpp | 1 + src/api/api_context.h | 3 ++ src/api/c++/z3++.h | 51 +++++++++++++++++++++++++++++++++ src/api/z3_api.h | 43 +++++++++++++++++++++++++++ src/ast/recfun_decl_plugin.cpp | 12 ++++---- src/ast/recfun_decl_plugin.h | 23 ++++++++------- src/cmd_context/cmd_context.cpp | 29 +++++-------------- src/cmd_context/cmd_context.h | 3 +- src/smt/theory_recfun.h | 3 +- 11 files changed, 191 insertions(+), 42 deletions(-) diff --git a/examples/c++/example.cpp b/examples/c++/example.cpp index 3089f5e2b..ab9c73209 100644 --- a/examples/c++/example.cpp +++ b/examples/c++/example.cpp @@ -1190,6 +1190,20 @@ void mk_model_example() { std::cout << m.eval(a + b < 2)<< std::endl; } +void recfun_example() { + std::cout << "recfun example\n"; + context c; + expr x = c.int_const("x"); + expr y = c.int_const("y"); + expr b = c.bool_const("b"); + sort I = c.int_sort(); + sort B = c.bool_sort(); + func_decl f = recfun("f", I, B, I); + expr_vector args(c); + args.push_back(x); args.push_back(b); + c.recdef(f, args, ite(b, x, f(x + 1, !b))); + prove(f(x,c.bool_val(false)) > x); +} int main() { @@ -1239,6 +1253,7 @@ int main() { consequence_example(); std::cout << "\n"; parse_example(); std::cout << "\n"; mk_model_example(); std::cout << "\n"; + recfun_example(); std::cout << "\n"; std::cout << "done\n"; } catch (exception & ex) { diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index a28315cda..cbe365c6c 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -38,6 +38,8 @@ Revision History: #include "util/cancel_eh.h" #include "util/scoped_timer.h" #include "ast/pp_params.hpp" +#include "ast/expr_abstract.h" + extern bool is_numeral_sort(Z3_context c, Z3_sort ty); @@ -110,6 +112,54 @@ extern "C" { Z3_CATCH_RETURN(nullptr); } + Z3_func_decl Z3_API Z3_mk_rec_func_decl(Z3_context c, Z3_symbol s, unsigned domain_size, Z3_sort const* domain, + Z3_sort range) { + Z3_TRY; + LOG_Z3_mk_rec_func_decl(c, s, domain_size, domain, range); + RESET_ERROR_CODE(); + // + recfun::promise_def def = + mk_c(c)->recfun().get_plugin().mk_def(to_symbol(s), + domain_size, + to_sorts(domain), + to_sort(range)); + func_decl* d = def.get_def()->get_decl(); + mk_c(c)->save_ast_trail(d); + RETURN_Z3(of_func_decl(d)); + Z3_CATCH_RETURN(nullptr); + } + + void Z3_API Z3_add_rec_def(Z3_context c, Z3_func_decl f, unsigned n, Z3_ast args[], Z3_ast body) { + Z3_TRY; + LOG_Z3_add_rec_def(c, f, n, args, body); + func_decl* d = to_func_decl(f); + ast_manager& m = mk_c(c)->m(); + recfun::decl::plugin& p = mk_c(c)->recfun().get_plugin(); + expr_ref abs_body(m); + expr_ref_vector _args(m); + var_ref_vector _vars(m); + for (unsigned i = 0; i < n; ++i) { + _args.push_back(to_expr(args[i])); + _vars.push_back(m.mk_var(n - i - 1, m.get_sort(_args.back()))); + if (m.get_sort(_args.back()) != d->get_domain(i)) { + SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); + return; + } + } + expr_abstract(m, 0, n, _args.c_ptr(), to_expr(body), abs_body); + recfun::promise_def pd = p.get_promise_def(d); + if (!pd.get_def()) { + SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); + return; + } + if (m.get_sort(abs_body) != d->get_range()) { + SET_ERROR_CODE(Z3_INVALID_ARG, nullptr); + return; + } + p.set_definition(pd, n, _vars.c_ptr(), abs_body); + Z3_CATCH; + } + Z3_ast Z3_API Z3_mk_app(Z3_context c, Z3_func_decl d, unsigned num_args, Z3_ast const * args) { Z3_TRY; LOG_Z3_mk_app(c, d, num_args, args); diff --git a/src/api/api_context.cpp b/src/api/api_context.cpp index c236ba3e8..9fe13a15f 100644 --- a/src/api/api_context.cpp +++ b/src/api/api_context.cpp @@ -79,6 +79,7 @@ namespace api { m_datalog_util(m()), m_fpa_util(m()), m_sutil(m()), + m_recfun(m()), m_last_result(m()), m_ast_trail(m()), m_pmanager(m_limit) { diff --git a/src/api/api_context.h b/src/api/api_context.h index a6f55d1aa..b04768710 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -29,6 +29,7 @@ Revision History: #include "ast/datatype_decl_plugin.h" #include "ast/dl_decl_plugin.h" #include "ast/fpa_decl_plugin.h" +#include "ast/recfun_decl_plugin.h" #include "smt/smt_kernel.h" #include "smt/params/smt_params.h" #include "util/event_handler.h" @@ -62,6 +63,7 @@ namespace api { datalog::dl_decl_util m_datalog_util; fpa_util m_fpa_util; seq_util m_sutil; + recfun_util m_recfun; // Support for old solver API smt_params m_fparams; @@ -128,6 +130,7 @@ namespace api { fpa_util & fpautil() { return m_fpa_util; } datatype_util& dtutil() { return m_dt_plugin->u(); } seq_util& sutil() { return m_sutil; } + recfun_util& recfun() { return m_recfun; } family_id get_basic_fid() const { return m_basic_fid; } family_id get_array_fid() const { return m_array_fid; } family_id get_arith_fid() const { return m_arith_fid; } diff --git a/src/api/c++/z3++.h b/src/api/c++/z3++.h index 07056746d..d360b6153 100644 --- a/src/api/c++/z3++.h +++ b/src/api/c++/z3++.h @@ -311,6 +311,13 @@ namespace z3 { func_decl function(char const * name, sort const & d1, sort const & d2, sort const & d3, sort const & d4, sort const & range); func_decl function(char const * name, sort const & d1, sort const & d2, sort const & d3, sort const & d4, sort const & d5, sort const & range); + func_decl recfun(symbol const & name, unsigned arity, sort const * domain, sort const & range); + func_decl recfun(char const * name, unsigned arity, sort const * domain, sort const & range); + func_decl recfun(char const * name, sort const & domain, sort const & range); + func_decl recfun(char const * name, sort const & d1, sort const & d2, sort const & range); + + void recdef(func_decl, expr_vector const& args, expr const& body); + expr constant(symbol const & name, sort const & s); expr constant(char const * name, sort const & s); expr bool_const(char const * name); @@ -2815,6 +2822,37 @@ namespace z3 { return func_decl(*this, f); } + inline func_decl context::recfun(symbol const & name, unsigned arity, sort const * domain, sort const & range) { + array args(arity); + for (unsigned i = 0; i < arity; i++) { + check_context(domain[i], range); + args[i] = domain[i]; + } + Z3_func_decl f = Z3_mk_rec_func_decl(m_ctx, name, arity, args.ptr(), range); + check_error(); + return func_decl(*this, f); + + } + + inline func_decl context::recfun(char const * name, unsigned arity, sort const * domain, sort const & range) { + return recfun(str_symbol(name), arity, domain, range); + } + + inline func_decl context::recfun(char const * name, sort const& d1, sort const & range) { + return recfun(str_symbol(name), 1, &d1, range); + } + + inline func_decl context::recfun(char const * name, sort const& d1, sort const& d2, sort const & range) { + sort dom[2] = { d1, d2 }; + return recfun(str_symbol(name), 2, dom, range); + } + + void context::recdef(func_decl f, expr_vector const& args, expr const& body) { + check_context(f, args); check_context(f, body); + array vars(args); + Z3_add_rec_def(f.ctx(), f, vars.size(), vars.ptr(), body); + } + inline expr context::constant(symbol const & name, sort const & s) { Z3_ast r = Z3_mk_const(m_ctx, name, s); check_error(); @@ -2976,6 +3014,19 @@ namespace z3 { return range.ctx().function(name.c_str(), domain, range); } + inline func_decl recfun(symbol const & name, unsigned arity, sort const * domain, sort const & range) { + return range.ctx().recfun(name, arity, domain, range); + } + inline func_decl recfun(char const * name, unsigned arity, sort const * domain, sort const & range) { + return range.ctx().recfun(name, arity, domain, range); + } + inline func_decl recfun(char const * name, sort const& d1, sort const & range) { + return range.ctx().recfun(name, d1, range); + } + inline func_decl recfun(char const * name, sort const& d1, sort const& d2, sort const & range) { + return range.ctx().recfun(name, d1, d2, range); + } + inline expr select(expr const & a, expr const & i) { check_context(a, i); Z3_ast r = Z3_mk_select(a.ctx(), a, i); diff --git a/src/api/z3_api.h b/src/api/z3_api.h index de446d9a8..a776bbe8e 100644 --- a/src/api/z3_api.h +++ b/src/api/z3_api.h @@ -2081,6 +2081,7 @@ extern "C" { Z3_sort range); + /** \brief Create a constant or function application. @@ -2140,6 +2141,48 @@ extern "C" { def_API('Z3_mk_fresh_const', AST, (_in(CONTEXT), _in(STRING), _in(SORT))) */ Z3_ast Z3_API Z3_mk_fresh_const(Z3_context c, Z3_string prefix, Z3_sort ty); + + + /** + \brief Declare a recursive function + + \param c logical context. + \param s name of the function. + \param domain_size number of arguments. It should be greater than 0. + \param domain array containing the sort of each argument. The array must contain domain_size elements. + \param range sort of the constant or the return sort of the function. + + After declaring recursive function, it should be associated with a recursive definition #Z3_mk_rec_def. + The function #Z3_mk_app can be used to create a constant or function + application. + + \sa Z3_mk_app + \sa Z3_mk_rec_def + + def_API('Z3_mk_rec_func_decl', FUNC_DECL, (_in(CONTEXT), _in(SYMBOL), _in(UINT), _in_array(2, SORT), _in(SORT))) + */ + Z3_func_decl Z3_API Z3_mk_rec_func_decl(Z3_context c, Z3_symbol s, + unsigned domain_size, Z3_sort const domain[], + Z3_sort range); + + /** + \brief Define the body of a recursive function. + + \param c logical context. + \param f function declaration. + \param n number of arguments to the function + \param args constants that are used as arguments to the recursive function in the definition. + \param body body of the recursive function + + After declaring a recursive function or a collection of mutually recursive functions, use + this function to provide the definition for the recursive function. + + \sa Z3_mk_rec_func_decl + + def_API('Z3_add_rec_def', VOID, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT), _in_array(2, AST), _in(AST))) + */ + void Z3_API Z3_add_rec_def(Z3_context c, Z3_func_decl f, unsigned n, Z3_ast args[], Z3_ast body); + /*@}*/ /** @name Propositional Logic and Equality */ diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 655462a1d..25f545ca4 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -312,8 +312,8 @@ namespace recfun { * Main manager for defined functions */ - util::util(ast_manager & m, family_id id) - : m_manager(m), m_fid(id), m_th_rw(m), + util::util(ast_manager & m) + : m_manager(m), m_fid(m.get_family_id("recfun")), m_th_rw(m), m_plugin(dynamic_cast(m.get_plugin(m_fid))) { } @@ -385,15 +385,15 @@ namespace recfun { SASSERT(m_manager); SASSERT(m_family_id != null_family_id); if (!m_util.get()) { - m_util = alloc(util, *m_manager, m_family_id); + m_util = alloc(util, *m_manager); } return *(m_util.get()); } promise_def plugin::mk_def(symbol const& name, unsigned n, sort *const * params, sort * range) { - SASSERT(! m_defs.contains(name)); def* d = u().decl_fun(name, n, params, range); - m_defs.insert(name, d); + SASSERT(! m_defs.contains(d->get_decl())); + m_defs.insert(d->get_decl(), d); return promise_def(&u(), d); } @@ -410,8 +410,8 @@ namespace recfun { def* plugin::mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs) { - SASSERT(! m_defs.contains(name)); promise_def d = mk_def(name, n, params, range); + SASSERT(! m_defs.contains(d.get_def()->get_decl())); set_definition(d, n_vars, vars, rhs); return d.get_def(); } diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index d93ad50b8..b516ae2bd 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -140,7 +140,7 @@ namespace recfun { namespace decl { class plugin : public decl_plugin { - typedef map def_map; + typedef obj_map def_map; typedef obj_map case_def_map; mutable scoped_ptr m_util; @@ -173,14 +173,14 @@ namespace recfun { def* mk_def(symbol const& name, unsigned n, sort ** params, sort * range, unsigned n_vars, var ** vars, expr * rhs); - bool has_def(const symbol& s) const { return m_defs.contains(s); } + bool has_def(func_decl* f) const { return m_defs.contains(f); } bool has_defs() const; - def const& get_def(const symbol& s) const { return *(m_defs[s]); } - promise_def get_promise_def(const symbol &s) const { return promise_def(&u(), m_defs[s]); } - def& get_def(symbol const& s) { return *(m_defs[s]); } + def const& get_def(func_decl* f) const { return *(m_defs[f]); } + promise_def get_promise_def(func_decl* f) const { return promise_def(&u(), m_defs[f]); } + def& get_def(func_decl* f) { return *(m_defs[f]); } bool has_case_def(func_decl* f) const { return m_case_defs.contains(f); } case_def& get_case_def(func_decl* f) { SASSERT(has_case_def(f)); return *(m_case_defs[f]); } - bool is_declared(symbol const& s) const { return m_defs.contains(s); } + //bool is_declared(symbol const& s) const { return m_defs.contains(s); } }; } @@ -197,7 +197,7 @@ namespace recfun { void set_definition(promise_def & d, unsigned n_vars, var * const * vars, expr * rhs); public: - util(ast_manager &m, family_id); + util(ast_manager &m); ~util(); ast_manager & m() { return m_manager; } @@ -213,9 +213,10 @@ namespace recfun { //has_def(s)); - return m_plugin->get_def(s); + + def& get_def(func_decl* f) { + SASSERT(m_plugin->has_def(f)); + return m_plugin->get_def(f); } case_def& get_case_def(expr* e) { @@ -232,6 +233,8 @@ namespace recfun { } app_ref mk_depth_limit_pred(unsigned d); + + decl::plugin& get_plugin() { return *m_plugin; } }; } diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 3031f8ed8..a4b949280 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -475,7 +475,6 @@ cmd_context::cmd_context(bool main_ctx, ast_manager * m, symbol const & l): m_manager(m), m_own_manager(m == nullptr), m_manager_initialized(false), - m_rec_fun_declared(false), m_pmanager(nullptr), m_sexpr_manager(nullptr), m_regular("stdout", std::cout), @@ -900,20 +899,15 @@ void cmd_context::model_del(func_decl* f) { } -recfun_decl_plugin * cmd_context::get_recfun_plugin() { - ast_manager & m = get_ast_manager(); - family_id id = m.get_family_id("recfun"); - recfun_decl_plugin* p = reinterpret_cast(m.get_plugin(id)); - SASSERT(p); - return p; +recfun_decl_plugin& cmd_context::get_recfun_plugin() { + recfun::util u(get_ast_manager()); + return u.get_plugin(); } recfun::promise_def cmd_context::decl_rec_fun(const symbol &name, unsigned int arity, sort *const *domain, sort *range) { SASSERT(logic_has_recfun()); - recfun_decl_plugin* p = get_recfun_plugin(); - recfun::promise_def def = p->mk_def(name, arity, domain, range); - return def; + return get_recfun_plugin().mk_def(name, arity, domain, range); } // insert a recursive function as a regular quantified axiom @@ -936,15 +930,6 @@ void cmd_context::insert_rec_fun_as_axiom(func_decl *f, expr_ref_vector const& b eq = m().mk_forall(ids.size(), f->get_domain(), ids.c_ptr(), eq, 0, m().rec_fun_qid(), symbol::null, 2, pats); } - // - // disable warning given the current way they are used - // (Z3 will here silently assume and not check the definitions to be well founded, - // and please use HSF for everything else). - // - if (false && !ids.empty() && !m_rec_fun_declared) { - warning_msg("recursive function definitions are assumed well-founded"); - m_rec_fun_declared = true; - } assert_expr(eq); } @@ -959,7 +944,7 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s TRACE("recfun", tout<< "define recfun " << f->get_name() << " = " << mk_pp(rhs, m()) << "\n";); - recfun_decl_plugin* p = get_recfun_plugin(); + recfun_decl_plugin& p = get_recfun_plugin(); var_ref_vector vars(m()); for (expr* b : binding) { @@ -967,8 +952,8 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s vars.push_back(to_var(b)); } - recfun::promise_def d = p->get_promise_def(f->get_name()); - p->set_definition(d, vars.size(), vars.c_ptr(), rhs); + recfun::promise_def d = p.get_promise_def(f); + p.set_definition(d, vars.size(), vars.c_ptr(), rhs); } func_decl * cmd_context::find_func_decl(symbol const & s) const { diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index acc406dfc..5e21a1bca 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -200,7 +200,6 @@ protected: ast_manager * m_manager; bool m_own_manager; bool m_manager_initialized; - bool m_rec_fun_declared; pdecl_manager * m_pmanager; sexpr_manager * m_sexpr_manager; check_logic m_check_logic; @@ -308,7 +307,7 @@ protected: void erase_macro(symbol const& s); bool macros_find(symbol const& s, unsigned n, expr*const* args, expr*& t) const; - recfun_decl_plugin * get_recfun_plugin(); + recfun_decl_plugin& get_recfun_plugin(); public: cmd_context(bool main_ctx = true, ast_manager * m = nullptr, symbol const & l = symbol::null); diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index b0d705ddc..56e738f21 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -42,8 +42,7 @@ namespace smt { m_lhs(n), m_def(nullptr), m_args() { SASSERT(u.is_defined(n)); func_decl * d = n->get_decl(); - const symbol& name = d->get_name(); - m_def = &u.get_def(name); + m_def = &u.get_def(d); m_args.append(n->get_num_args(), n->get_args()); } case_expansion(case_expansion const & from) From 80acf8ed79bb18d2cd11e49476dea73c65976b8e Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 27 Oct 2018 13:26:32 -0500 Subject: [PATCH 59/65] add recfuns to model Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 3 ++- src/ast/ast.h | 2 +- src/ast/recfun_decl_plugin.cpp | 3 ++- src/ast/recfun_decl_plugin.h | 16 ++++++++++++---- src/ast/rewriter/rewriter.h | 5 +++-- src/ast/rewriter/rewriter_def.h | 6 ++---- src/smt/params/smt_params_helper.pyg | 2 +- src/smt/smt_context.cpp | 21 +++++++++++++++++++++ src/smt/theory_recfun.cpp | 2 +- 9 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index e2ff19776..9a15bc7d3 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1360,7 +1360,8 @@ ast_manager::ast_manager(ast_manager const & src, bool disable_proofs): m_proof_mode(disable_proofs ? PGM_DISABLED : src.m_proof_mode), m_trace_stream(src.m_trace_stream), m_trace_stream_owner(false), - m_rec_fun(":rec-fun") { + m_rec_fun(":rec-fun"), + m_lambda_def(":lambda-def") { SASSERT(!src.is_format_manager()); m_format_manager = alloc(ast_manager, PGM_DISABLED, m_trace_stream, true); init(); diff --git a/src/ast/ast.h b/src/ast/ast.h index 37a6126f2..9a0e223de 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1636,7 +1636,7 @@ public: bool are_distinct(expr * a, expr * b) const; bool contains(ast * a) const { return m_ast_table.contains(a); } - + bool is_rec_fun_def(quantifier* q) const { return q->get_qid() == m_rec_fun; } bool is_lambda_def(quantifier* q) const { return q->get_qid() == m_lambda_def; } func_decl* get_rec_fun_decl(quantifier* q) const; diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index 25f545ca4..ca929a9d7 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -56,6 +56,7 @@ namespace recfun { m_domain(m, arity, domain), m_range(range, m), m_vars(m), m_cases(), m_decl(m), + m_rhs(m), m_fid(fid) { SASSERT(arity == get_arity()); @@ -211,7 +212,7 @@ namespace recfun { name.append(m_name.bare_str()); m_vars.append(n_vars, vars); - + m_rhs = rhs; expr_ref_vector conditions(m); diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index b516ae2bd..6bd338f54 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -99,6 +99,7 @@ namespace recfun { vars m_vars; //get_rec_funs(); + } + app_ref mk_depth_limit_pred(unsigned d); decl::plugin& get_plugin() { return *m_plugin; } diff --git a/src/ast/rewriter/rewriter.h b/src/ast/rewriter/rewriter.h index 84fef488f..4195e7de6 100644 --- a/src/ast/rewriter/rewriter.h +++ b/src/ast/rewriter/rewriter.h @@ -279,8 +279,9 @@ protected: return false; } - bool get_macro(func_decl * f, expr * & def, quantifier * & q, proof * & def_pr) { - return m_cfg.get_macro(f, def, q, def_pr); + bool get_macro(func_decl * f, expr * & def, proof * & def_pr) { + quantifier* q = nullptr; + return m_cfg.get_macro(f, def, q, def_pr); } void push_frame(expr * t, bool mcache, unsigned max_depth) { diff --git a/src/ast/rewriter/rewriter_def.h b/src/ast/rewriter/rewriter_def.h index ebe52f52a..3ee1e4caf 100644 --- a/src/ast/rewriter/rewriter_def.h +++ b/src/ast/rewriter/rewriter_def.h @@ -212,6 +212,7 @@ bool rewriter_tpl::constant_fold(app * t, frame & fr) { return false; } + template template void rewriter_tpl::process_app(app * t, frame & fr) { @@ -338,15 +339,12 @@ void rewriter_tpl::process_app(app * t, frame & fr) { // TODO: add rewrite rules support expr * def = nullptr; proof * def_pr = nullptr; - quantifier * def_q = nullptr; // When get_macro succeeds, then // we know that: // forall X. f(X) = def[X] // and def_pr is a proof for this quantifier. // - // Remark: def_q is only used for proof generation. - // It is the quantifier forall X. f(X) = def[X] - if (get_macro(f, def, def_q, def_pr)) { + if (get_macro(f, def, def_pr)) { SASSERT(!f->is_associative() || !flat_assoc(f)); SASSERT(new_num_args == t->get_num_args()); SASSERT(m().get_sort(def) == m().get_sort(t)); diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index 2f87f24c0..6b4aee1c3 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -96,6 +96,6 @@ def_module_params(module_name='smt', ('core.extend_nonlocal_patterns', BOOL, False, 'extend unsat cores with literals that have quantifiers with patterns that contain symbols which are not in the quantifier\'s body'), ('lemma_gc_strategy', UINT, 0, 'lemma garbage collection strategy: 0 - fixed, 1 - geometric, 2 - at restart, 3 - none'), ('dt_lazy_splits', UINT, 1, 'How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy'), - ('recfun.native', BOOL, False, 'use native rec-fun solver'), + ('recfun.native', BOOL, True, 'use native rec-fun solver'), ('recfun.depth', UINT, 2, 'initial depth for maxrec expansion') )) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index a56110bc2..15887920a 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -37,6 +37,7 @@ Revision History: #include "model/model_pp.h" #include "ast/ast_smt2_pp.h" #include "ast/ast_translation.h" +#include "ast/recfun_decl_plugin.h" namespace smt { @@ -4453,6 +4454,26 @@ namespace smt { m_model->register_decl(f, fi); } } + recfun::util u(m); + recfun::decl::plugin& p = u.get_plugin(); + func_decl_ref_vector recfuns = u.get_rec_funs(); + for (func_decl* f : recfuns) { + auto& def = u.get_def(f); + expr* rhs = def.get_rhs(); + if (!rhs) continue; + func_interp* fi = alloc(func_interp, m, f->get_arity()); + // reverse argument order so that variable 0 starts at the beginning. + expr_ref_vector subst(m); + unsigned idx = 0; + for (unsigned i = 0; i < f->get_arity(); ++i) { + subst.push_back(m.mk_var(i, f->get_domain(i))); + } + var_subst sub(m, true); + expr_ref bodyr = sub(rhs, subst.size(), subst.c_ptr()); + + fi->set_else(bodyr); + m_model->register_decl(f, fi); + } } }; diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 1c1ee964d..88354f1eb 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -306,7 +306,7 @@ namespace smt { auto & vars = e.m_def->get_vars(); expr_ref lhs(e.m_lhs, m); unsigned depth = get_depth(e.m_lhs); - expr_ref rhs(apply_args(depth, vars, e.m_args, e.m_def->get_macro_rhs()), m); + expr_ref rhs(apply_args(depth, vars, e.m_args, e.m_def->get_rhs()), m); literal lit = mk_eq_lit(lhs, rhs); ctx().mk_th_axiom(get_id(), 1, &lit); TRACEFN("macro expansion yields " << mk_pp(rhs, m) << "\n" << From 7db58be9046b4fa463c79cc9058a5aa2e4f92455 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sat, 27 Oct 2018 16:14:20 -0500 Subject: [PATCH 60/65] add recfuns to python API Signed-off-by: Nikolaj Bjorner --- src/api/python/z3/z3.py | 47 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 63b5a9e72..f68b908e2 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -800,6 +800,49 @@ def Function(name, *sig): def _to_func_decl_ref(a, ctx): return FuncDeclRef(a, ctx) +def RecFunction(name, *sig): + """Create a new Z3 recursive with the given sorts.""" + sig = _get_args(sig) + if __debug__: + _z3_assert(len(sig) > 0, "At least two arguments expected") + arity = len(sig) - 1 + rng = sig[arity] + if __debug__: + _z3_assert(is_sort(rng), "Z3 sort expected") + dom = (Sort * arity)() + for i in range(arity): + if __debug__: + _z3_assert(is_sort(sig[i]), "Z3 sort expected") + dom[i] = sig[i].ast + ctx = rng.ctx + return FuncDeclRef(Z3_mk_rec_func_decl(ctx.ref(), to_symbol(name, ctx), arity, dom, rng.ast), ctx) + +def RecAddDefinition(f, args, body): + """Set the body of a recursive function. + Recursive definitions are only unfolded during search. + >>> ctx = Context() + >>> fac = RecFunction('fac', IntSort(ctx), IntSort(ctx)) + >>> n = Int('n', ctx) + >>> RecAddDefinition(fac, n, If(n == 0, 1, n*fac(n-1))) + >>> simplify(fac(5)) + fac(5) + >>> s = Solver(ctx=ctx) + >>> s.add(fac(n) < 3) + >>> s.check() + sat + >>> s.model().eval(fac(5)) + 120 + """ + if is_app(args): + args = [args] + ctx = body.ctx + args = _get_args(args) + n = len(args) + _args = (Ast * n)() + for i in range(n): + _args[i] = args[i].ast + Z3_add_rec_def(ctx.ref(), f.ast, n, _args, body.ast) + ######################################### # # Expressions @@ -6512,8 +6555,8 @@ class Solver(Z3PPObject): >>> s.add(x > 0, x < 2) >>> s.check() sat - >>> s.model() - [x = 1] + >>> s.model().eval(x) + 1 >>> s.add(x < 1) >>> s.check() unsat From 0f0287d129e7a87e096e55c8dd15e7e224aa96c4 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Sun, 28 Oct 2018 17:42:16 -0500 Subject: [PATCH 61/65] prepare release notes Signed-off-by: Nikolaj Bjorner --- CMakeLists.txt | 2 +- RELEASE_NOTES | 14 +++++++++++++ scripts/mk_project.py | 2 +- src/api/api_context.h | 4 ++-- src/api/dotnet/Context.cs | 28 +++++++++++++++++++++++++ src/api/dotnet/FuncDecl.cs | 9 ++++++++ src/ast/ast_pp_util.cpp | 10 ++++++++- src/ast/ast_pp_util.h | 2 +- src/ast/ast_smt2_pp.cpp | 37 +++++++++++++++++++++++++++++++++ src/ast/ast_smt2_pp.h | 2 ++ src/ast/ast_smt_pp.cpp | 8 ------- src/ast/decl_collector.cpp | 8 ++----- src/ast/decl_collector.h | 7 +------ src/ast/recfun_decl_plugin.cpp | 2 +- src/ast/recfun_decl_plugin.h | 13 +++++------- src/ast/reg_decl_plugins.cpp | 2 +- src/cmd_context/cmd_context.cpp | 12 +++++------ src/cmd_context/cmd_context.h | 2 +- src/model/model_smt2_pp.cpp | 29 +++++++++++++------------- src/smt/theory_recfun.cpp | 2 +- src/smt/theory_recfun.h | 16 +++++++------- 21 files changed, 144 insertions(+), 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28ff64568..cd9c14435 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ endif() ################################################################################ set(Z3_VERSION_MAJOR 4) set(Z3_VERSION_MINOR 8) -set(Z3_VERSION_PATCH 2) +set(Z3_VERSION_PATCH 3) set(Z3_VERSION_TWEAK 0) set(Z3_VERSION "${Z3_VERSION_MAJOR}.${Z3_VERSION_MINOR}.${Z3_VERSION_PATCH}.${Z3_VERSION_TWEAK}") set(Z3_FULL_VERSION_STR "${Z3_VERSION}") # Note this might be modified diff --git a/RELEASE_NOTES b/RELEASE_NOTES index acdb627d8..dcb8fc093 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,5 +1,19 @@ RELEASE NOTES +Version 4.8.3 +============= +- New features + - native handling of recursive function definitions + - PB rounding based option for conflict resolution when reasoning about PB constraints. + +Version 4.8.2 +============= +- Post-Release. + +Version 4.8.1 +============= +- Release. Bug-fix for 4.8.0 + Version 4.8.0 ============= diff --git a/scripts/mk_project.py b/scripts/mk_project.py index 18d57e729..5bd96c0fc 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -9,7 +9,7 @@ from mk_util import * # Z3 Project definition def init_project_def(): - set_version(4, 8, 2, 0) + set_version(4, 8, 3, 0) add_lib('util', [], includes2install = ['z3_version.h']) add_lib('polynomial', ['util'], 'math/polynomial') add_lib('sat', ['util']) diff --git a/src/api/api_context.h b/src/api/api_context.h index b04768710..a6b8600d6 100644 --- a/src/api/api_context.h +++ b/src/api/api_context.h @@ -63,7 +63,7 @@ namespace api { datalog::dl_decl_util m_datalog_util; fpa_util m_fpa_util; seq_util m_sutil; - recfun_util m_recfun; + recfun::util m_recfun; // Support for old solver API smt_params m_fparams; @@ -130,7 +130,7 @@ namespace api { fpa_util & fpautil() { return m_fpa_util; } datatype_util& dtutil() { return m_dt_plugin->u(); } seq_util& sutil() { return m_sutil; } - recfun_util& recfun() { return m_recfun; } + recfun::util& recfun() { return m_recfun; } family_id get_basic_fid() const { return m_basic_fid; } family_id get_array_fid() const { return m_array_fid; } family_id get_arith_fid() const { return m_arith_fid; } diff --git a/src/api/dotnet/Context.cs b/src/api/dotnet/Context.cs index 97541e31f..ac11022c1 100644 --- a/src/api/dotnet/Context.cs +++ b/src/api/dotnet/Context.cs @@ -532,6 +532,34 @@ namespace Microsoft.Z3 return new FuncDecl(this, MkSymbol(name), domain, range); } + /// + /// Creates a new recursive function declaration. + /// + public FuncDecl MkRecFuncDecl(string name, Sort[] domain, Sort range) + { + Debug.Assert(range != null); + Debug.Assert(domain.All(d => d != null)); + + CheckContextMatch(domain); + CheckContextMatch(range); + return new FuncDecl(this, MkSymbol(name), domain, range, true); + } + + /// + /// Bind a definition to a recursive function declaration. + /// The function must have previously been created using + /// MkRecFuncDecl. The body may contain recursive uses of the function or + /// other mutually recursive functions. + /// + public void AddRecDef(FuncDecl f, Expr[] args, Expr body) + { + CheckContextMatch(f); + CheckContextMatch(args); + CheckContextMatch(body); + IntPtr[] argsNative = AST.ArrayToNative(args); + Native.Z3_add_rec_def(nCtx, f.NativeObject, (uint)args.Length, argsNative, body.NativeObject); + } + /// /// Creates a new function declaration. /// diff --git a/src/api/dotnet/FuncDecl.cs b/src/api/dotnet/FuncDecl.cs index 24ae456d8..30ed790db 100644 --- a/src/api/dotnet/FuncDecl.cs +++ b/src/api/dotnet/FuncDecl.cs @@ -302,6 +302,15 @@ namespace Microsoft.Z3 Debug.Assert(range != null); } + internal FuncDecl(Context ctx, Symbol name, Sort[] domain, Sort range, bool is_rec) + : base(ctx, Native.Z3_mk_rec_func_decl(ctx.nCtx, name.NativeObject, AST.ArrayLength(domain), AST.ArrayToNative(domain), range.NativeObject)) + { + Debug.Assert(ctx != null); + Debug.Assert(name != null); + Debug.Assert(range != null); + } + + #if DEBUG internal override void CheckNativeObject(IntPtr obj) { diff --git a/src/ast/ast_pp_util.cpp b/src/ast/ast_pp_util.cpp index 378710b36..a57ea6040 100644 --- a/src/ast/ast_pp_util.cpp +++ b/src/ast/ast_pp_util.cpp @@ -20,6 +20,7 @@ Revision History: #include "ast/ast_pp_util.h" #include "ast/ast_smt2_pp.h" #include "ast/ast_smt_pp.h" +#include "ast/recfun_decl_plugin.h" void ast_pp_util::collect(expr* e) { coll.visit(e); @@ -49,7 +50,14 @@ void ast_pp_util::display_decls(std::ostream& out) { ast_smt2_pp(out, f, m_env) << "\n"; } } - SASSERT(coll.get_num_preds() == 0); + vector> recfuns; + recfun::util u(m); + func_decl_ref_vector funs = u.get_rec_funs(); + if (funs.empty()) return; + for (func_decl * f : funs) { + recfuns.push_back(std::make_pair(f, u.get_def(f).get_rhs())); + } + ast_smt2_pp_recdefs(out, recfuns, m_env); } void ast_pp_util::remove_decl(func_decl* f) { diff --git a/src/ast/ast_pp_util.h b/src/ast/ast_pp_util.h index c3b8aa601..e70880672 100644 --- a/src/ast/ast_pp_util.h +++ b/src/ast/ast_pp_util.h @@ -31,7 +31,7 @@ class ast_pp_util { decl_collector coll; - ast_pp_util(ast_manager& m): m(m), m_env(m), coll(m, false) {} + ast_pp_util(ast_manager& m): m(m), m_env(m), coll(m) {} void collect(expr* e); diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index 3a55bbb0b..5f2d36279 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -1163,6 +1163,33 @@ public: unregister_var_names(f->get_arity()); } + // format set of mutually recursive definitions + void operator()(vector> const& funs, format_ref & r) { + format_ref_vector decls(m()), bodies(m()); + format_ref r1(m()), r2(m()); + + for (auto const& p : funs) { + unsigned len; + func_decl* f = p.first; + expr* e = p.second; + format * fname = m_env.pp_fdecl_name(f, len); + register_var_names(f->get_arity()); + format * args[3]; + args[0] = fname; + args[1] = pp_var_args(f->get_arity(), f->get_domain()); + args[2] = m_env.pp_sort(f->get_range()); + decls.push_back(mk_seq1(m(), args, args+3, f2f(), "")); + process(e, r); + bodies.push_back(r); + unregister_var_names(f->get_arity()); + } + r1 = mk_seq1(m(), decls.begin(), decls.end(), f2f(), ""); + r2 = mk_seq1(m(), bodies.begin(), bodies.end(), f2f(), ""); + format * args[2]; + args[0] = r1; + args[1] = r2; + r = mk_seq1(m(), args, args+2, f2f(), "define-rec-funs"); + } }; @@ -1275,6 +1302,16 @@ std::ostream & ast_smt2_pp(std::ostream & out, symbol const& s, bool is_skolem, return out; } +std::ostream & ast_smt2_pp_recdefs(std::ostream & out, vector> const& funs, smt2_pp_environment & env, params_ref const & p) { + ast_manager & m = env.get_manager(); + format_ref r(fm(m)); + smt2_printer pr(env, p); + pr(funs, r); + pp(out, r.get(), m, p); + return out; +} + + mk_ismt2_pp::mk_ismt2_pp(ast * t, ast_manager & m, params_ref const & p, unsigned indent, unsigned num_vars, char const * var_prefix): m_ast(t), m_manager(m), diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index 13093f138..dc306c6df 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -105,6 +105,8 @@ std::ostream & ast_smt2_pp(std::ostream & out, sort * s, smt2_pp_environment & e std::ostream & ast_smt2_pp(std::ostream & out, func_decl * f, smt2_pp_environment & env, params_ref const & p = params_ref(), unsigned indent = 0, char const* cmd = "declare-fun"); std::ostream & ast_smt2_pp(std::ostream & out, func_decl * f, expr* e, smt2_pp_environment & env, params_ref const & p = params_ref(), unsigned indent = 0, char const* cmd = "define-fun"); std::ostream & ast_smt2_pp(std::ostream & out, symbol const& s, bool is_skolem, smt2_pp_environment & env, params_ref const& p = params_ref()); +std::ostream & ast_smt2_pp_recdefs(std::ostream & out, vector> const& funs, smt2_pp_environment & env, params_ref const & p = params_ref()); + /** \brief Internal wrapper (for debugging purposes only) diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index 5e2828adf..227e00419 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -980,14 +980,6 @@ void ast_smt_pp::display_smt2(std::ostream& strm, expr* n) { } } - for (unsigned i = 0; i < decls.get_num_preds(); ++i) { - func_decl* d = decls.get_pred_decls()[i]; - if (!(*m_is_declared)(d)) { - smt_printer p(strm, m, ql, rn, m_logic, true, true, m_simplify_implies, 0); - p(d); - strm << "\n"; - } - } #endif for (expr* a : m_assumptions) { diff --git a/src/ast/decl_collector.cpp b/src/ast/decl_collector.cpp index bec60e61c..bc415c461 100644 --- a/src/ast/decl_collector.cpp +++ b/src/ast/decl_collector.cpp @@ -50,18 +50,14 @@ void decl_collector::visit_func(func_decl * n) { if (!m_visited.is_marked(n)) { family_id fid = n->get_family_id(); if (fid == null_family_id) { - if (m_sep_preds && is_bool(n->get_range())) - m_preds.push_back(n); - else - m_decls.push_back(n); + m_decls.push_back(n); } m_visited.mark(n, true); } } -decl_collector::decl_collector(ast_manager & m, bool preds): +decl_collector::decl_collector(ast_manager & m): m_manager(m), - m_sep_preds(preds), m_dt_util(m) { m_basic_fid = m_manager.get_basic_family_id(); m_dt_fid = m_dt_util.get_family_id(); diff --git a/src/ast/decl_collector.h b/src/ast/decl_collector.h index 07539dbd8..8945c0bec 100644 --- a/src/ast/decl_collector.h +++ b/src/ast/decl_collector.h @@ -26,10 +26,8 @@ Revision History: class decl_collector { ast_manager & m_manager; - bool m_sep_preds; ptr_vector m_sorts; ptr_vector m_decls; - ptr_vector m_preds; ast_mark m_visited; family_id m_basic_fid; family_id m_dt_fid; @@ -46,8 +44,7 @@ class decl_collector { public: - // if preds == true, then predicates are stored in a separate collection. - decl_collector(ast_manager & m, bool preds = true); + decl_collector(ast_manager & m); ast_manager & m() { return m_manager; } void visit_func(func_decl* n); @@ -59,11 +56,9 @@ public: unsigned get_num_sorts() const { return m_sorts.size(); } unsigned get_num_decls() const { return m_decls.size(); } - unsigned get_num_preds() const { return m_preds.size(); } sort * const * get_sorts() const { return m_sorts.c_ptr(); } func_decl * const * get_func_decls() const { return m_decls.c_ptr(); } - func_decl * const * get_pred_decls() const { return m_preds.c_ptr(); } }; #endif diff --git a/src/ast/recfun_decl_plugin.cpp b/src/ast/recfun_decl_plugin.cpp index ca929a9d7..7fe7ae00b 100644 --- a/src/ast/recfun_decl_plugin.cpp +++ b/src/ast/recfun_decl_plugin.cpp @@ -369,7 +369,7 @@ namespace recfun { } namespace decl { - plugin::plugin() : decl_plugin(), m_defs(), m_case_defs(), m_def_block() {} + plugin::plugin() : decl_plugin(), m_defs(), m_case_defs() {} plugin::~plugin() { finalize(); } void plugin::finalize() { diff --git a/src/ast/recfun_decl_plugin.h b/src/ast/recfun_decl_plugin.h index 6bd338f54..347689a37 100644 --- a/src/ast/recfun_decl_plugin.h +++ b/src/ast/recfun_decl_plugin.h @@ -143,9 +143,8 @@ namespace recfun { typedef obj_map case_def_map; mutable scoped_ptr m_util; - def_map m_defs; // function->def - case_def_map m_case_defs; // case_pred->def - svector m_def_block; + def_map m_defs; // function->def + case_def_map m_case_defs; // case_pred->def ast_manager & m() { return *m_manager; } public: @@ -206,8 +205,11 @@ namespace recfun { ast_manager & m() { return m_manager; } th_rewriter & get_th_rewriter() { return m_th_rw; } + decl::plugin& get_plugin() { return *m_plugin; } + bool is_case_pred(expr * e) const { return is_app_of(e, m_fid, OP_FUN_CASE_PRED); } bool is_defined(expr * e) const { return is_app_of(e, m_fid, OP_FUN_DEFINED); } + bool is_defined(func_decl* f) const { return is_decl_of(f, m_fid, OP_FUN_DEFINED); } bool is_depth_limit(expr * e) const { return is_app_of(e, m_fid, OP_DEPTH_LIMIT); } bool owns_app(app * e) const { return e->get_family_id() == m_fid; } @@ -242,11 +244,6 @@ namespace recfun { app_ref mk_depth_limit_pred(unsigned d); - decl::plugin& get_plugin() { return *m_plugin; } }; } -typedef recfun::def recfun_def; -typedef recfun::case_def recfun_case_def; -typedef recfun::decl::plugin recfun_decl_plugin; -typedef recfun::util recfun_util; diff --git a/src/ast/reg_decl_plugins.cpp b/src/ast/reg_decl_plugins.cpp index f8abe81d8..7e121eb7f 100644 --- a/src/ast/reg_decl_plugins.cpp +++ b/src/ast/reg_decl_plugins.cpp @@ -42,7 +42,7 @@ void reg_decl_plugins(ast_manager & m) { m.register_plugin(symbol("datatype"), alloc(datatype_decl_plugin)); } if (!m.get_plugin(m.mk_family_id(symbol("recfun")))) { - m.register_plugin(symbol("recfun"), alloc(recfun_decl_plugin)); + m.register_plugin(symbol("recfun"), alloc(recfun::decl::plugin)); } if (!m.get_plugin(m.mk_family_id(symbol("datalog_relation")))) { m.register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin)); diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index a4b949280..db0164445 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -693,7 +693,7 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("bv"), alloc(bv_decl_plugin), logic_has_bv()); register_plugin(symbol("array"), alloc(array_decl_plugin), logic_has_array()); register_plugin(symbol("datatype"), alloc(datatype_decl_plugin), logic_has_datatype()); - register_plugin(symbol("recfun"), alloc(recfun_decl_plugin), logic_has_recfun()); + register_plugin(symbol("recfun"), alloc(recfun::decl::plugin), logic_has_recfun()); register_plugin(symbol("seq"), alloc(seq_decl_plugin), logic_has_seq()); register_plugin(symbol("pb"), alloc(pb_decl_plugin), logic_has_pb()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); @@ -899,7 +899,7 @@ void cmd_context::model_del(func_decl* f) { } -recfun_decl_plugin& cmd_context::get_recfun_plugin() { +recfun::decl::plugin& cmd_context::get_recfun_plugin() { recfun::util u(get_ast_manager()); return u.get_plugin(); } @@ -944,7 +944,7 @@ void cmd_context::insert_rec_fun(func_decl* f, expr_ref_vector const& binding, s TRACE("recfun", tout<< "define recfun " << f->get_name() << " = " << mk_pp(rhs, m()) << "\n";); - recfun_decl_plugin& p = get_recfun_plugin(); + recfun::decl::plugin& p = get_recfun_plugin(); var_ref_vector vars(m()); for (expr* b : binding) { @@ -2040,7 +2040,7 @@ void cmd_context::display_smt2_benchmark(std::ostream & out, unsigned num, expr if (logic != symbol::null) out << "(set-logic " << logic << ")" << std::endl; // collect uninterpreted function declarations - decl_collector decls(m(), false); + decl_collector decls(m()); for (unsigned i = 0; i < num; i++) { decls.visit(assertions[i]); } @@ -2071,8 +2071,8 @@ void cmd_context::slow_progress_sample() { svector labels; m_solver->get_labels(labels); regular_stream() << "(labels"; - for (unsigned i = 0; i < labels.size(); i++) { - regular_stream() << " " << labels[i]; + for (symbol const& s : labels) { + regular_stream() << " " << s; } regular_stream() << "))" << std::endl; } diff --git a/src/cmd_context/cmd_context.h b/src/cmd_context/cmd_context.h index 5e21a1bca..b2ed1e5cb 100644 --- a/src/cmd_context/cmd_context.h +++ b/src/cmd_context/cmd_context.h @@ -307,7 +307,7 @@ protected: void erase_macro(symbol const& s); bool macros_find(symbol const& s, unsigned n, expr*const* args, expr*& t) const; - recfun_decl_plugin& get_recfun_plugin(); + recfun::decl::plugin& get_recfun_plugin(); public: cmd_context(bool main_ctx = true, ast_manager * m = nullptr, symbol const & l = symbol::null); diff --git a/src/model/model_smt2_pp.cpp b/src/model/model_smt2_pp.cpp index 807e2b4e7..b2a4f02aa 100644 --- a/src/model/model_smt2_pp.cpp +++ b/src/model/model_smt2_pp.cpp @@ -21,6 +21,7 @@ Revision History: #include "model/model_smt2_pp.h" #include "ast/ast_smt2_pp.h" #include "ast/func_decl_dependencies.h" +#include "ast/recfun_decl_plugin.h" #include "ast/pp.h" using namespace format_ns; @@ -60,11 +61,9 @@ static void pp_uninterp_sorts(std::ostream & out, ast_printer_context & ctx, mod ctx.display(buffer, s, 13); buffer << ":\n"; pp_indent(buffer, TAB_SZ); - ptr_vector::const_iterator it = u.begin(); - ptr_vector::const_iterator end = u.end(); - for (; it != end; ++it) { - SASSERT(is_app(*it)); - app * c = to_app(*it); + for (expr* e : u) { + SASSERT(is_app(e)); + app * c = to_app(e); pp_symbol(buffer, c->get_decl()->get_name()); buffer << " "; } @@ -87,9 +86,8 @@ static void pp_uninterp_sorts(std::ostream & out, ast_printer_context & ctx, mod out << "\n"; pp_indent(out, indent); out << ";; definitions for universe elements:\n"; - it = u.begin(); - for (; it != end; ++it) { - app * c = to_app(*it); + for (expr * e : u) { + app * c = to_app(e); pp_indent(out, indent); out << "(declare-fun "; unsigned len = pp_symbol(out, c->get_decl()->get_name()); @@ -101,9 +99,8 @@ static void pp_uninterp_sorts(std::ostream & out, ast_printer_context & ctx, mod out << ";; cardinality constraint:\n"; f_conds.reset(); format * var = mk_string(m, "x"); - it = u.begin(); - for (; it != end; ++it) { - app * c = to_app(*it); + for (expr* e : u) { + app * c = to_app(e); symbol csym = c->get_decl()->get_name(); std::string cname; if (is_smt2_quoted_symbol(csym)) @@ -170,10 +167,7 @@ void sort_fun_decls(ast_manager & m, model_core const & md, ptr_bufferget_else(), deps); - func_decl_set::iterator it2 = deps.begin(); - func_decl_set::iterator end2 = deps.end(); - for (; it2 != end2; ++it2) { - func_decl * d = *it2; + for (func_decl * d : deps) { if (d->get_arity() > 0 && md.has_interpretation(d) && !visited.contains(d)) { todo.push_back(d); visited.insert(d); @@ -189,8 +183,10 @@ void sort_fun_decls(ast_manager & m, model_core const & md, ptr_buffer var_names; ptr_buffer f_var_names; ptr_buffer f_arg_decls; @@ -200,6 +196,9 @@ static void pp_funs(std::ostream & out, ast_printer_context & ctx, model_core co sort_fun_decls(m, md, func_decls); for (unsigned i = 0; i < func_decls.size(); i++) { func_decl * f = func_decls[i]; + if (recfun_util.is_defined(f)) { + continue; + } func_interp * f_i = md.get_func_interp(f); SASSERT(f->get_arity() == f_i->get_arity()); format_ref body(fm(m)); diff --git a/src/smt/theory_recfun.cpp b/src/smt/theory_recfun.cpp index 88354f1eb..c3e7c997e 100644 --- a/src/smt/theory_recfun.cpp +++ b/src/smt/theory_recfun.cpp @@ -31,7 +31,7 @@ namespace smt { theory_recfun::theory_recfun(ast_manager & m) : theory(m.mk_family_id("recfun")), m(m), - m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), + m_plugin(*reinterpret_cast(m.get_plugin(get_family_id()))), m_util(m_plugin.u()), m_preds(m), m_max_depth(0), diff --git a/src/smt/theory_recfun.h b/src/smt/theory_recfun.h index 56e738f21..46392c6d2 100644 --- a/src/smt/theory_recfun.h +++ b/src/smt/theory_recfun.h @@ -36,9 +36,9 @@ namespace smt { // one case-expansion of `f(t1...tn)` struct case_expansion { app * m_lhs; // the term to expand - recfun_def * m_def; + recfun::def * m_def; ptr_vector m_args; - case_expansion(recfun_util& u, app * n) : + case_expansion(recfun::util& u, app * n) : m_lhs(n), m_def(nullptr), m_args() { SASSERT(u.is_defined(n)); func_decl * d = n->get_decl(); @@ -66,14 +66,14 @@ namespace smt { // one body-expansion of `f(t1...tn)` using a `C_f_i(t1...tn)` struct body_expansion { app* m_pred; - recfun_case_def const * m_cdef; + recfun::case_def const * m_cdef; ptr_vector m_args; - body_expansion(recfun_util& u, app * n) : m_pred(n), m_cdef(0), m_args() { + body_expansion(recfun::util& u, app * n) : m_pred(n), m_cdef(0), m_args() { m_cdef = &u.get_case_def(n); m_args.append(n->get_num_args(), n->get_args()); } - body_expansion(app* pred, recfun_case_def const & d, ptr_vector & args) : + body_expansion(app* pred, recfun::case_def const & d, ptr_vector & args) : m_pred(pred), m_cdef(&d), m_args(args) {} body_expansion(body_expansion const & from): m_pred(from.m_pred), m_cdef(from.m_cdef), m_args(from.m_args) {} @@ -90,8 +90,8 @@ namespace smt { friend std::ostream& operator<<(std::ostream&, pp_body_expansion const &); ast_manager& m; - recfun_decl_plugin& m_plugin; - recfun_util& m_util; + recfun::decl::plugin& m_plugin; + recfun::util& m_util; stats m_stats; // book-keeping for depth of predicates @@ -104,7 +104,7 @@ namespace smt { ptr_vector m_q_body_expand; vector m_q_clauses; - recfun_util & u() const { return m_util; } + recfun::util & u() const { return m_util; } bool is_defined(app * f) const { return u().is_defined(f); } bool is_case_pred(app * f) const { return u().is_case_pred(f); } From 2b14ec215b738b027c74d411f32fc549574d98b7 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 30 Oct 2018 17:22:55 -0500 Subject: [PATCH 62/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index 9a15bc7d3..cc9605fc9 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1549,7 +1549,6 @@ void ast_manager::raise_exception(std::string const& msg) { std::ostream& ast_manager::display(std::ostream& out, parameter const& p) { switch (p.get_kind()) { case parameter::PARAM_AST: - std::cout << "ast: " << p.get_ast() << "\n"; return out << mk_pp(p.get_ast(), *this); default: return p.display(out); From 22d2458c93d4822be81d9ba1cb3cbe2c76782211 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 30 Oct 2018 18:23:10 -0500 Subject: [PATCH 63/65] na Signed-off-by: Nikolaj Bjorner --- src/ast/ast.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/ast/ast.h b/src/ast/ast.h index efe452619..4054b32dc 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -2501,15 +2501,6 @@ public: void mark(ast * n, bool flag) { if (flag) mark(n); else reset_mark(n); } }; -struct parameter_pp { - parameter const& p; - ast_manager& m; - parameter_pp(parameter const& p, ast_manager& m): p(p), m(m) {} -}; - -inline std::ostream& operator<<(std::ostream& out, parameter_pp const& pp) { - return pp.m.display(out, pp.p); -} typedef ast_ref_fast_mark<1> ast_ref_fast_mark1; typedef ast_ref_fast_mark<2> ast_ref_fast_mark2; From bcf896bd0359e75bd46ac5fbb090f7bd708c10da Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Tue, 30 Oct 2018 18:25:03 -0500 Subject: [PATCH 64/65] display' Signed-off-by: Nikolaj Bjorner --- src/ast/ast.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index d72bcbe82..9a5e1de9c 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -1546,16 +1546,6 @@ void ast_manager::raise_exception(std::string const& msg) { throw ast_exception(msg.c_str()); } -std::ostream& ast_manager::display(std::ostream& out, parameter const& p) { - switch (p.get_kind()) { - case parameter::PARAM_AST: - return out << mk_pp(p.get_ast(), *this); - default: - return p.display(out); - } - return out; -} - std::ostream& ast_manager::display(std::ostream& out, parameter const& p) { switch (p.get_kind()) { From a775d1f5189f9fa7f10f51ba6fbdfaade1367da1 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Wed, 31 Oct 2018 14:40:13 -0500 Subject: [PATCH 65/65] newline Signed-off-by: Nikolaj Bjorner --- src/util/uint_set.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/uint_set.h b/src/util/uint_set.h index e6518b435..202df1039 100644 --- a/src/util/uint_set.h +++ b/src/util/uint_set.h @@ -270,7 +270,8 @@ public: if (contains(v)) { m_in_set[v] = false; unsigned i = m_set.size(); - for (; i > 0 && m_set[--i] != v; ) ; + for (; i > 0 && m_set[--i] != v; ) + ; SASSERT(m_set[i] == v); m_set[i] = m_set.back(); m_set.pop_back();