diff --git a/.travis.yml b/.travis.yml index a3ffb221e..9ec6132b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,7 @@ matrix: - os: osx osx_image: xcode8.3 # Note: Apple Clang does not support OpenMP - env: Z3_BUILD_TYPE=RelWithDebInfo USE_OPENMP=0 + env: Z3_BUILD_TYPE=RelWithDebInfo USE_OPENMP=0 DOTNET_BINDINGS=0 script: # Use `travis_wait` when doing LTO builds because this configuration will # have long link times during which it will not show any output which diff --git a/RELEASE_NOTES b/RELEASE_NOTES index 7439342de..e55b329db 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -26,12 +26,16 @@ Version 4.8.0 (known as ACCE, ABCE, CCE). Asymmetric branching also uses features introduced in Lingeling by exploiting binary implication graphs. Use sat.acce=true to enable the full repertoire of inprocessing methods. By default, clauses that are "eliminated" by acce are tagged as lemmas (redundant) and are garbage collected if their glue level is high. + - Substantial overhaul of the spacer horn clause engine. - Removed features: - interpolation API - duality engine for constrained Horn clauses. + - pdr engine for constrained Horn clauses. The engine's functionality has been + folded into spacer as one of optional strategies. - long deprecated API functions have been removed from z3_api.h + Version 4.7.1 ============= diff --git a/cmake/modules/FindDotNetToolchain.cmake b/cmake/modules/FindDotNetToolchain.cmake index abc4ff21c..6e8cc7610 100644 --- a/cmake/modules/FindDotNetToolchain.cmake +++ b/cmake/modules/FindDotNetToolchain.cmake @@ -33,13 +33,18 @@ if (DOTNET_CSC_EXECUTABLE) set(DOTNET_TOOLCHAIN_IS_MONO TRUE) set(DOTNET_TOOLCHAIN_IS_WINDOWS FALSE) message(STATUS ".NET toolchain is Mono") + elseif ("${CSC_STD_OUT}" MATCHES "^Turbo[ ]+C#") + set(DOTNET_DETERMINED_VENDOR TRUE) + set(DOTNET_TOOLCHAIN_IS_MONO TRUE) + set(DOTNET_TOOLCHAIN_IS_WINDOWS FALSE) + message(STATUS ".NET toolchain is Mono") elseif ("${CSC_STD_OUT}" MATCHES "^Microsoft.+Visual[ ]+C#") set(DOTNET_DETERMINED_VENDOR TRUE) set(DOTNET_TOOLCHAIN_IS_MONO FALSE) set(DOTNET_TOOLCHAIN_IS_WINDOWS TRUE) message(STATUS ".NET toolchain is Windows native") else() - message(STATUS ".NET toolchain is unknown") + message(STATUS ".NET toolchain is unknown: ${CSC_STD_OUT}") endif() endif() endif() diff --git a/scripts/mk_project.py b/scripts/mk_project.py index f2720c893..dada93069 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -41,7 +41,6 @@ def init_project_def(): add_lib('aig_tactic', ['tactic'], 'tactic/aig') add_lib('ackermannization', ['model', 'rewriter', 'ast', 'solver', 'tactic'], 'ackermannization') add_lib('cmd_context', ['solver', 'rewriter']) - add_lib('extra_cmds', ['cmd_context', 'subpaving_tactic', 'arith_tactics'], 'cmd_context/extra_cmds') add_lib('smt2parser', ['cmd_context', 'parser_util'], 'parsers/smt2') add_lib('fpa', ['ast', 'util', 'rewriter', 'model'], 'ast/fpa') add_lib('pattern', ['normal_forms', 'smt2parser', 'rewriter'], 'ast/pattern') @@ -59,13 +58,12 @@ def init_project_def(): add_lib('dataflow', ['muz'], 'muz/dataflow') add_lib('transforms', ['muz', 'hilbert', 'dataflow'], 'muz/transforms') add_lib('rel', ['muz', 'transforms'], 'muz/rel') - add_lib('pdr', ['muz', 'transforms', 'arith_tactics', 'core_tactics', 'smt_tactic'], 'muz/pdr') add_lib('spacer', ['muz', 'transforms', 'arith_tactics', 'smt_tactic'], 'muz/spacer') add_lib('clp', ['muz', 'transforms'], 'muz/clp') add_lib('tab', ['muz', 'transforms'], 'muz/tab') add_lib('bmc', ['muz', 'transforms'], 'muz/bmc') add_lib('ddnf', ['muz', 'transforms', 'rel'], 'muz/ddnf') - add_lib('fp', ['muz', 'pdr', 'clp', 'tab', 'rel', 'bmc', 'ddnf', 'spacer'], 'muz/fp') + add_lib('fp', ['muz', 'clp', 'tab', 'rel', 'bmc', 'ddnf', 'spacer'], 'muz/fp') add_lib('ufbv_tactic', ['normal_forms', 'core_tactics', 'macros', 'smt_tactic', 'rewriter'], 'tactic/ufbv') add_lib('sat_solver', ['solver', 'core_tactics', 'aig_tactic', 'bv_tactics', 'arith_tactics', 'sat_tactic'], 'sat/sat_solver') add_lib('smtlogic_tactics', ['ackermannization', 'sat_solver', 'arith_tactics', 'bv_tactics', 'nlsat_tactic', 'smt_tactic', 'aig_tactic', 'fp', 'muz','qe'], 'tactic/smtlogics') @@ -75,6 +73,7 @@ def init_project_def(): API_files = ['z3_api.h', 'z3_ast_containers.h', 'z3_algebraic.h', 'z3_polynomial.h', 'z3_rcf.h', 'z3_fixedpoint.h', 'z3_optimization.h', 'z3_fpa.h', 'z3_spacer.h'] add_lib('api', ['portfolio', 'realclosure', 'opt'], includes2install=['z3.h', 'z3_v1.h', 'z3_macros.h'] + API_files) + add_lib('extra_cmds', ['cmd_context', 'subpaving_tactic', 'qe', 'arith_tactics'], 'cmd_context/extra_cmds') add_exe('shell', ['api', 'sat', 'extra_cmds','opt'], exe_name='z3') add_exe('test', ['api', 'fuzzing', 'simplex'], exe_name='test-z3', install=False) _libz3Component = add_dll('api_dll', ['api', 'sat', 'extra_cmds'], 'api/dll', diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9020c9e4b..7dee4039a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,7 +81,6 @@ add_subdirectory(muz/base) add_subdirectory(muz/dataflow) add_subdirectory(muz/transforms) add_subdirectory(muz/rel) -add_subdirectory(muz/pdr) add_subdirectory(muz/clp) add_subdirectory(muz/tab) add_subdirectory(muz/bmc) diff --git a/src/api/api_ast.cpp b/src/api/api_ast.cpp index 536b94fb2..34168f1f4 100644 --- a/src/api/api_ast.cpp +++ b/src/api/api_ast.cpp @@ -190,7 +190,7 @@ extern "C" { MK_UNARY(Z3_mk_not, mk_c(c)->get_basic_fid(), OP_NOT, SKIP); MK_BINARY(Z3_mk_eq, mk_c(c)->get_basic_fid(), OP_EQ, SKIP); MK_NARY(Z3_mk_distinct, mk_c(c)->get_basic_fid(), OP_DISTINCT, SKIP); - MK_BINARY(Z3_mk_iff, mk_c(c)->get_basic_fid(), OP_IFF, SKIP); + MK_BINARY(Z3_mk_iff, mk_c(c)->get_basic_fid(), OP_EQ, SKIP); MK_BINARY(Z3_mk_implies, mk_c(c)->get_basic_fid(), OP_IMPLIES, SKIP); MK_BINARY(Z3_mk_xor, mk_c(c)->get_basic_fid(), OP_XOR, SKIP); MK_NARY(Z3_mk_and, mk_c(c)->get_basic_fid(), OP_AND, SKIP); @@ -894,7 +894,6 @@ extern "C" { case OP_ITE: return Z3_OP_ITE; case OP_AND: return Z3_OP_AND; case OP_OR: return Z3_OP_OR; - case OP_IFF: return Z3_OP_IFF; case OP_XOR: return Z3_OP_XOR; case OP_NOT: return Z3_OP_NOT; case OP_IMPLIES: return Z3_OP_IMPLIES; diff --git a/src/api/api_datalog.cpp b/src/api/api_datalog.cpp index 2bc3d01e1..fd31a65f8 100644 --- a/src/api/api_datalog.cpp +++ b/src/api/api_datalog.cpp @@ -603,7 +603,26 @@ extern "C" { Z3_CATCH; } - + + void Z3_API Z3_fixedpoint_add_callback(Z3_context c, Z3_fixedpoint d, + void *state, + Z3_fixedpoint_new_lemma_eh new_lemma_eh, + Z3_fixedpoint_predecessor_eh predecessor_eh, + Z3_fixedpoint_unfold_eh unfold_eh){ + Z3_TRY; + // not logged + to_fixedpoint_ref(d)->ctx().add_callback(state, + reinterpret_cast(new_lemma_eh), + reinterpret_cast(predecessor_eh), + reinterpret_cast(unfold_eh)); + + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_add_constraint (Z3_context c, Z3_fixedpoint d, Z3_ast e, unsigned lvl){ + to_fixedpoint_ref(d)->ctx().add_constraint(to_expr(e), lvl); + } + #include "api_datalog_spacer.inc" }; diff --git a/src/api/api_model.cpp b/src/api/api_model.cpp index 6318283c6..7eb7d2fdd 100644 --- a/src/api/api_model.cpp +++ b/src/api/api_model.cpp @@ -166,7 +166,8 @@ extern "C" { CHECK_IS_EXPR(t, Z3_FALSE); model * _m = to_model_ref(m); expr_ref result(mk_c(c)->m()); - _m->eval(to_expr(t), result, model_completion == Z3_TRUE); + model::scoped_model_completion _scm(*_m, model_completion == Z3_TRUE); + result = (*_m)(to_expr(t)); mk_c(c)->save_ast_trail(result.get()); *v = of_ast(result.get()); RETURN_Z3_model_eval Z3_TRUE; diff --git a/src/api/api_qe.cpp b/src/api/api_qe.cpp index 2516aacfb..92517b02b 100644 --- a/src/api/api_qe.cpp +++ b/src/api/api_qe.cpp @@ -52,7 +52,7 @@ extern "C" Z3_TRY; LOG_Z3_qe_model_project (c, m, num_bounds, bound, body); RESET_ERROR_CODE(); - + app_ref_vector vars(mk_c(c)->m ()); if (!to_apps(num_bounds, bound, vars)) { SET_ERROR_CODE (Z3_INVALID_ARG); @@ -62,7 +62,7 @@ extern "C" expr_ref result (mk_c(c)->m ()); result = to_expr (body); model_ref model (to_model_ref (m)); - spacer::qe_project (mk_c(c)->m (), vars, result, model); + spacer::qe_project (mk_c(c)->m (), vars, result, *model); mk_c(c)->save_ast_trail (result.get ()); return of_expr (result.get ()); @@ -119,11 +119,8 @@ extern "C" facts.push_back (to_expr (fml)); flatten_and (facts); - spacer::model_evaluator_util mev (mk_c(c)->m()); - mev.set_model (*model); - expr_ref_vector lits (mk_c(c)->m()); - spacer::compute_implicant_literals (mev, facts, lits); + spacer::compute_implicant_literals (*model, facts, lits); expr_ref result (mk_c(c)->m ()); result = mk_and (lits); diff --git a/src/api/api_solver.cpp b/src/api/api_solver.cpp index 657ff025c..951b9e51e 100644 --- a/src/api/api_solver.cpp +++ b/src/api/api_solver.cpp @@ -460,12 +460,12 @@ extern "C" { LOG_Z3_solver_get_unsat_core(c, s); RESET_ERROR_CODE(); init_solver(c, s); - ptr_vector core; + expr_ref_vector core(mk_c(c)->m()); to_solver_ref(s)->get_unsat_core(core); Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, *mk_c(c), mk_c(c)->m()); mk_c(c)->save_object(v); - for (unsigned i = 0; i < core.size(); i++) { - v->m_ast_vector.push_back(core[i]); + for (expr* e : core) { + v->m_ast_vector.push_back(e); } RETURN_Z3(of_ast_vector(v)); Z3_CATCH_RETURN(nullptr); @@ -536,24 +536,22 @@ extern "C" { init_solver(c, s); expr_ref_vector _assumptions(m), _consequences(m), _variables(m); ast_ref_vector const& __assumptions = to_ast_vector_ref(assumptions); - unsigned sz = __assumptions.size(); - for (unsigned i = 0; i < sz; ++i) { - if (!is_expr(__assumptions[i])) { + for (ast* e : __assumptions) { + if (!is_expr(e)) { _assumptions.finalize(); _consequences.finalize(); _variables.finalize(); SET_ERROR_CODE(Z3_INVALID_USAGE); return Z3_L_UNDEF; } - _assumptions.push_back(to_expr(__assumptions[i])); + _assumptions.push_back(to_expr(e)); } ast_ref_vector const& __variables = to_ast_vector_ref(variables); - sz = __variables.size(); - for (unsigned i = 0; i < sz; ++i) { - if (!is_expr(__variables[i])) { + for (ast* a : __variables) { + if (!is_expr(a)) { _assumptions.finalize(); _consequences.finalize(); _variables.finalize(); SET_ERROR_CODE(Z3_INVALID_USAGE); return Z3_L_UNDEF; } - _variables.push_back(to_expr(__variables[i])); + _variables.push_back(to_expr(a)); } lbool result = l_undef; unsigned timeout = to_solver(s)->m_params.get_uint("timeout", mk_c(c)->get_timeout()); @@ -578,8 +576,8 @@ extern "C" { if (result == l_undef) { to_solver_ref(s)->set_reason_unknown(eh); } - for (unsigned i = 0; i < _consequences.size(); ++i) { - to_ast_vector_ref(consequences).push_back(_consequences[i].get()); + for (expr* e : _consequences) { + to_ast_vector_ref(consequences).push_back(e); } return static_cast(result); Z3_CATCH_RETURN(Z3_L_UNDEF); diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index 7657fb347..5e83a4181 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -90,6 +90,9 @@ def _z3_assert(cond, msg): if not cond: raise Z3Exception(msg) +def _z3_check_cint_overflow(n, name): + _z3_assert(ctypes.c_int(n).value == n, name + " is too large") + def open_log(fname): """Log interaction to a file. This function must be invoked immediately after init(). """ Z3_open_log(fname) @@ -1898,13 +1901,17 @@ def is_quantifier(a): def _mk_quantifier(is_forall, vs, body, weight=1, qid="", skid="", patterns=[], no_patterns=[]): if __debug__: - _z3_assert(is_bool(body), "Z3 expression expected") + _z3_assert(is_bool(body) or is_app(vs) or (len(vs) > 0 and is_app(vs[0])), "Z3 expression expected") _z3_assert(is_const(vs) or (len(vs) > 0 and all([ is_const(v) for v in vs])), "Invalid bounded variable(s)") _z3_assert(all([is_pattern(a) or is_expr(a) for a in patterns]), "Z3 patterns expected") - _z3_assert(all([is_expr(p) for p in no_patterns]), "no patterns are Z3 expressions") - ctx = body.ctx + _z3_assert(all([is_expr(p) for p in no_patterns]), "no patterns are Z3 expressions") if is_app(vs): + ctx = vs.ctx vs = [vs] + else: + ctx = vs[0].ctx + if not is_expr(body): + body = BoolVal(body, ctx) num_vars = len(vs) if num_vars == 0: return body @@ -8128,6 +8135,7 @@ def _pb_args_coeffs(args, default_ctx = None): _args, sz = _to_ast_array(args) _coeffs = (ctypes.c_int * len(coeffs))() for i in range(len(coeffs)): + _z3_check_cint_overflow(coeffs[i], "coefficient") _coeffs[i] = coeffs[i] return ctx, sz, _args, _coeffs @@ -8137,6 +8145,7 @@ def PbLe(args, k): >>> a, b, c = Bools('a b c') >>> f = PbLe(((a,1),(b,3),(c,2)), 3) """ + _z3_check_cint_overflow(k, "k") ctx, sz, _args, _coeffs = _pb_args_coeffs(args) return BoolRef(Z3_mk_pble(ctx.ref(), sz, _args, _coeffs, k), ctx) @@ -8146,6 +8155,7 @@ def PbGe(args, k): >>> a, b, c = Bools('a b c') >>> f = PbGe(((a,1),(b,3),(c,2)), 3) """ + _z3_check_cint_overflow(k, "k") ctx, sz, _args, _coeffs = _pb_args_coeffs(args) return BoolRef(Z3_mk_pbge(ctx.ref(), sz, _args, _coeffs, k), ctx) @@ -8155,6 +8165,7 @@ def PbEq(args, k, ctx = None): >>> a, b, c = Bools('a b c') >>> f = PbEq(((a,1),(b,3),(c,2)), 3) """ + _z3_check_cint_overflow(k, "k") ctx, sz, _args, _coeffs = _pb_args_coeffs(args) return BoolRef(Z3_mk_pbeq(ctx.ref(), sz, _args, _coeffs, k), ctx) @@ -8847,7 +8858,10 @@ class FPNumRef(FPRef): 1.25 """ def significand_as_long(self): - return Z3_fpa_get_numeral_significand_uint64(self.ctx.ref(), self.as_ast()) + ptr = (ctypes.c_ulonglong * 1)() + if not Z3_fpa_get_numeral_significand_uint64(self.ctx.ref(), self.as_ast(), ptr): + raise Z3Exception("error retrieving the significand of a numeral.") + return ptr[0] """The significand of the numeral as a bit-vector expression. diff --git a/src/api/z3_fixedpoint.h b/src/api/z3_fixedpoint.h index a651993ce..b4c3eee49 100644 --- a/src/api/z3_fixedpoint.h +++ b/src/api/z3_fixedpoint.h @@ -367,6 +367,18 @@ extern "C" { void Z3_API Z3_fixedpoint_set_reduce_app_callback( Z3_context c, Z3_fixedpoint d, Z3_fixedpoint_reduce_app_callback_fptr cb); + typedef void (*Z3_fixedpoint_new_lemma_eh)(void *state, Z3_ast lemma, unsigned level); + typedef void (*Z3_fixedpoint_predecessor_eh)(void *state); + typedef void (*Z3_fixedpoint_unfold_eh)(void *state); + + /** \brief set export callback for lemmas */ + void Z3_API Z3_fixedpoint_add_callback(Z3_context ctx, Z3_fixedpoint f, void *state, + Z3_fixedpoint_new_lemma_eh new_lemma_eh, + Z3_fixedpoint_predecessor_eh predecessor_eh, + Z3_fixedpoint_unfold_eh unfold_eh); + + void Z3_fixedpoint_add_constraint (Z3_context c, Z3_fixedpoint d, Z3_ast e, unsigned lvl); + /*@}*/ /*@}*/ diff --git a/src/ast/CMakeLists.txt b/src/ast/CMakeLists.txt index 4dcdd2a35..80543bb05 100644 --- a/src/ast/CMakeLists.txt +++ b/src/ast/CMakeLists.txt @@ -24,7 +24,6 @@ z3_add_component(ast expr_map.cpp expr_stat.cpp expr_substitution.cpp - factor_equivs.cpp for_each_ast.cpp for_each_expr.cpp format.cpp diff --git a/src/ast/arith_decl_plugin.cpp b/src/ast/arith_decl_plugin.cpp index 688edbcd5..fe5bc3af5 100644 --- a/src/ast/arith_decl_plugin.cpp +++ b/src/ast/arith_decl_plugin.cpp @@ -81,7 +81,7 @@ app * arith_decl_plugin::mk_numeral(algebraic_numbers::anum const & val, bool is return mk_numeral(rval, is_int); } else { - if (is_int) { + if (is_int) { m_manager->raise_exception("invalid irrational value passed as an integer"); } unsigned idx = aw().mk_id(val); @@ -638,6 +638,35 @@ bool arith_recognizers::is_numeral(expr const * n, rational & val, bool & is_int return true; } +#define IS_INT_EXPR_DEPTH_LIMIT 100 +bool arith_recognizers::is_int_expr(expr const *e) const { + if (is_int(e)) return true; + if (is_uninterp(e)) return false; + ptr_buffer todo; + todo.push_back(e); + rational r; + unsigned i = 0; + while (!todo.empty()) { + ++i; + if (i > IS_INT_EXPR_DEPTH_LIMIT) {return false;} + e = todo.back(); + todo.pop_back(); + if (is_to_real(e)) { + // pass + } + else if (is_numeral(e, r) && r.is_int()) { + // pass + } + else if (is_add(e) || is_mul(e)) { + todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + } + else { + return false; + } + } + return true; +} + arith_util::arith_util(ast_manager & m): arith_recognizers(m.mk_family_id("arith")), m_manager(m), diff --git a/src/ast/arith_decl_plugin.h b/src/ast/arith_decl_plugin.h index 6cebdaded..d7340297b 100644 --- a/src/ast/arith_decl_plugin.h +++ b/src/ast/arith_decl_plugin.h @@ -244,6 +244,8 @@ public: return false; } + bool is_int_expr(expr const * e) const; + bool is_le(expr const * n) const { return is_app_of(n, m_afid, OP_LE); } bool is_ge(expr const * n) const { return is_app_of(n, m_afid, OP_GE); } bool is_lt(expr const * n) const { return is_app_of(n, m_afid, OP_LT); } @@ -533,4 +535,3 @@ inline app_ref operator>(app_ref const& x, app_ref const& y) { } #endif /* ARITH_DECL_PLUGIN_H_ */ - diff --git a/src/ast/ast.cpp b/src/ast/ast.cpp index cae1618c0..681f64a25 100644 --- a/src/ast/ast.cpp +++ b/src/ast/ast.cpp @@ -643,7 +643,6 @@ basic_decl_plugin::basic_decl_plugin(): m_false_decl(nullptr), m_and_decl(nullptr), m_or_decl(nullptr), - m_iff_decl(nullptr), m_xor_decl(nullptr), m_not_decl(nullptr), m_implies_decl(nullptr), @@ -861,7 +860,6 @@ void basic_decl_plugin::set_manager(ast_manager * m, family_id id) { m_false_decl = mk_bool_op_decl("false", OP_FALSE); m_and_decl = mk_bool_op_decl("and", OP_AND, 2, true, true, true, true); m_or_decl = mk_bool_op_decl("or", OP_OR, 2, true, true, true, true); - m_iff_decl = mk_bool_op_decl("iff", OP_IFF, 2, false, true, false, false, true); m_xor_decl = mk_bool_op_decl("xor", OP_XOR, 2, true, true); m_not_decl = mk_bool_op_decl("not", OP_NOT, 1); m_implies_decl = mk_implies_decl(); @@ -892,13 +890,13 @@ void basic_decl_plugin::get_op_names(svector & op_names, symbol co if (logic == symbol::null) { // user friendly aliases op_names.push_back(builtin_name("implies", OP_IMPLIES)); - op_names.push_back(builtin_name("iff", OP_IFF)); + op_names.push_back(builtin_name("iff", OP_EQ)); op_names.push_back(builtin_name("if_then_else", OP_ITE)); op_names.push_back(builtin_name("if", OP_ITE)); op_names.push_back(builtin_name("&&", OP_AND)); op_names.push_back(builtin_name("||", OP_OR)); op_names.push_back(builtin_name("equals", OP_EQ)); - op_names.push_back(builtin_name("equiv", OP_IFF)); + op_names.push_back(builtin_name("equiv", OP_EQ)); } } @@ -919,7 +917,6 @@ void basic_decl_plugin::finalize() { DEC_REF(m_and_decl); DEC_REF(m_or_decl); DEC_REF(m_not_decl); - DEC_REF(m_iff_decl); DEC_REF(m_xor_decl); DEC_REF(m_implies_decl); DEC_ARRAY_REF(m_eq_decls); @@ -1051,7 +1048,6 @@ func_decl * basic_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters case OP_AND: return m_and_decl; case OP_OR: return m_or_decl; case OP_NOT: return m_not_decl; - case OP_IFF: return m_iff_decl; case OP_IMPLIES: return m_implies_decl; case OP_XOR: return m_xor_decl; case OP_ITE: return arity == 3 ? mk_ite_decl(join(domain[1], domain[2])) : nullptr; @@ -1093,7 +1089,6 @@ func_decl * basic_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters case OP_AND: return m_and_decl; case OP_OR: return m_or_decl; case OP_NOT: return m_not_decl; - case OP_IFF: return m_iff_decl; case OP_IMPLIES: return m_implies_decl; case OP_XOR: return m_xor_decl; case OP_ITE: return num_args == 3 ? mk_ite_decl(join(m_manager->get_sort(args[1]), m_manager->get_sort(args[2]))): nullptr; @@ -2636,10 +2631,10 @@ proof * ast_manager::mk_modus_ponens(proof * p1, proof * p2) { if (!p1 || !p2) return nullptr; SASSERT(has_fact(p1)); SASSERT(has_fact(p2)); - CTRACE("mk_modus_ponens", !(is_implies(get_fact(p2)) || is_iff(get_fact(p2)) || is_oeq(get_fact(p2))), + CTRACE("mk_modus_ponens", !(is_implies(get_fact(p2)) || is_eq(get_fact(p2)) || is_oeq(get_fact(p2))), tout << mk_ll_pp(p1, *this) << "\n"; tout << mk_ll_pp(p2, *this) << "\n";); - SASSERT(is_implies(get_fact(p2)) || is_iff(get_fact(p2)) || is_oeq(get_fact(p2))); + SASSERT(is_implies(get_fact(p2)) || is_eq(get_fact(p2)) || is_oeq(get_fact(p2))); CTRACE("mk_modus_ponens", to_app(get_fact(p2))->get_arg(0) != get_fact(p1), tout << mk_pp(get_fact(p1), *this) << "\n" << mk_pp(get_fact(p2), *this) << "\n";); SASSERT(to_app(get_fact(p2))->get_arg(0) == get_fact(p1)); @@ -2717,8 +2712,6 @@ proof * ast_manager::mk_transitivity(proof * p1, proof * p2) { tout << mk_pp(to_app(get_fact(p1))->get_decl(), *this) << "\n"; tout << mk_pp(to_app(get_fact(p2))->get_decl(), *this) << "\n";); SASSERT(to_app(get_fact(p1))->get_decl() == to_app(get_fact(p2))->get_decl() || - ((is_iff(get_fact(p1)) || is_eq(get_fact(p1))) && - (is_iff(get_fact(p2)) || is_eq(get_fact(p2)))) || ( (is_eq(get_fact(p1)) || is_oeq(get_fact(p1))) && (is_eq(get_fact(p2)) || is_oeq(get_fact(p2))))); CTRACE("mk_transitivity", to_app(get_fact(p1))->get_arg(1) != to_app(get_fact(p2))->get_arg(0), @@ -2797,7 +2790,7 @@ proof * ast_manager::mk_quant_intro(quantifier * q1, quantifier * q2, proof * p) if (!p) return nullptr; SASSERT(q1->get_num_decls() == q2->get_num_decls()); SASSERT(has_fact(p)); - SASSERT(is_iff(get_fact(p))); + SASSERT(is_eq(get_fact(p))); return mk_app(m_basic_family_id, PR_QUANT_INTRO, p, mk_iff(q1, q2)); } @@ -2884,8 +2877,7 @@ bool ast_manager::is_quant_inst(expr const* e, expr*& not_q_or_i, ptr_vectorget_arg(0), r1, r2) || - is_iff(to_app(e)->get_arg(0), r1, r2)); + VERIFY (is_eq(to_app(e)->get_arg(0), r1, r2)); return true; } else { @@ -2913,7 +2905,7 @@ proof * ast_manager::mk_unit_resolution(unsigned num_proofs, proof * const * pro fact = mk_false(); } else { - CTRACE("mk_unit_resolution_bug", !is_or(f1), tout << mk_pp(f1, *this) << " " << mk_pp(f2, *this) << "\n";); + CTRACE("mk_unit_resolution_bug", !is_or(f1), tout << mk_ll_pp(f1, *this) << "\n" << mk_ll_pp(f2, *this) << "\n";); SASSERT(is_or(f1)); ptr_buffer new_lits; app const * cls = to_app(f1); @@ -3044,7 +3036,7 @@ proof * ast_manager::mk_iff_oeq(proof * p) { if (!p) return p; SASSERT(has_fact(p)); - SASSERT(is_iff(get_fact(p)) || is_oeq(get_fact(p))); + SASSERT(is_eq(get_fact(p)) || is_oeq(get_fact(p))); if (is_oeq(get_fact(p))) return p; diff --git a/src/ast/ast.h b/src/ast/ast.h index e85f164e1..b6b71c6a3 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1041,7 +1041,7 @@ enum basic_sort_kind { }; enum basic_op_kind { - OP_TRUE, OP_FALSE, OP_EQ, OP_DISTINCT, OP_ITE, OP_AND, OP_OR, OP_IFF, OP_XOR, OP_NOT, OP_IMPLIES, OP_OEQ, LAST_BASIC_OP, + OP_TRUE, OP_FALSE, OP_EQ, OP_DISTINCT, OP_ITE, OP_AND, OP_OR, OP_XOR, OP_NOT, OP_IMPLIES, OP_OEQ, LAST_BASIC_OP, PR_UNDEF, PR_TRUE, PR_ASSERTED, PR_GOAL, PR_MODUS_PONENS, PR_REFLEXIVITY, PR_SYMMETRY, PR_TRANSITIVITY, PR_TRANSITIVITY_STAR, PR_MONOTONICITY, PR_QUANT_INTRO, PR_DISTRIBUTIVITY, PR_AND_ELIM, PR_NOT_OR_ELIM, PR_REWRITE, PR_REWRITE_STAR, PR_PULL_QUANT, @@ -1060,7 +1060,6 @@ protected: func_decl * m_false_decl; func_decl * m_and_decl; func_decl * m_or_decl; - func_decl * m_iff_decl; func_decl * m_xor_decl; func_decl * m_not_decl; func_decl * m_implies_decl; @@ -1344,9 +1343,9 @@ public: bool is_and(expr const * n) const { return is_app_of(n, m_fid, OP_AND); } bool is_not(expr const * n) const { return is_app_of(n, m_fid, OP_NOT); } bool is_eq(expr const * n) const { return is_app_of(n, m_fid, OP_EQ); } + bool is_iff(expr const* n) const { return is_eq(n) && is_bool(to_app(n)->get_arg(0)); } bool is_oeq(expr const * n) const { return is_app_of(n, m_fid, OP_OEQ); } bool is_distinct(expr const * n) const { return is_app_of(n, m_fid, OP_DISTINCT); } - bool is_iff(expr const * n) const { return is_app_of(n, m_fid, OP_IFF); } bool is_xor(expr const * n) const { return is_app_of(n, m_fid, OP_XOR); } bool is_ite(expr const * n) const { return is_app_of(n, m_fid, OP_ITE); } bool is_term_ite(expr const * n) const { return is_ite(n) && !is_bool(n); } @@ -1361,7 +1360,6 @@ public: bool is_and(func_decl const * d) const { return is_decl_of(d, m_fid, OP_AND); } bool is_not(func_decl const * d) const { return is_decl_of(d, m_fid, OP_NOT); } bool is_eq(func_decl const * d) const { return is_decl_of(d, m_fid, OP_EQ); } - bool is_iff(func_decl const * d) const { return is_decl_of(d, m_fid, OP_IFF); } bool is_xor(func_decl const * d) const { return is_decl_of(d, m_fid, OP_XOR); } bool is_ite(func_decl const * d) const { return is_decl_of(d, m_fid, OP_ITE); } bool is_term_ite(func_decl const * d) const { return is_ite(d) && !is_bool(d->get_range()); } @@ -1369,13 +1367,13 @@ public: MATCH_UNARY(is_not); MATCH_BINARY(is_eq); - MATCH_BINARY(is_iff); MATCH_BINARY(is_implies); MATCH_BINARY(is_and); MATCH_BINARY(is_or); MATCH_BINARY(is_xor); MATCH_TERNARY(is_and); MATCH_TERNARY(is_or); + bool is_iff(expr const* n, expr*& lhs, expr*& rhs) const { return is_eq(n, lhs, rhs) && is_bool(lhs); } bool is_ite(expr const * n, expr * & t1, expr * & t2, expr * & t3) const; }; @@ -1663,7 +1661,7 @@ public: bool is_bool(expr const * n) const; bool is_bool(sort const * s) const { return s == m_bool_sort; } - decl_kind get_eq_op(expr const * n) const { return is_bool(n) ? OP_IFF : OP_EQ; } + decl_kind get_eq_op(expr const * n) const { return OP_EQ; } private: sort * mk_sort(symbol const & name, sort_info * info); @@ -1746,6 +1744,14 @@ public: arity, domain); } + func_decl * mk_const_decl(const char* name, sort * s) { + return mk_func_decl(symbol(name), static_cast(0), nullptr, s); + } + + func_decl * mk_const_decl(std::string const& name, sort * s) { + return mk_func_decl(symbol(name.c_str()), static_cast(0), nullptr, s); + } + func_decl * mk_const_decl(symbol const & name, sort * s) { return mk_func_decl(name, static_cast(0), nullptr, s); } @@ -1812,6 +1818,14 @@ public: return mk_const(mk_const_decl(name, s)); } + app * mk_const(std::string const & name, sort * s) { + return mk_const(mk_const_decl(name, s)); + } + + app * mk_const(char const* name, sort * s) { + return mk_const(mk_const_decl(name, s)); + } + func_decl * mk_fresh_func_decl(symbol const & prefix, symbol const & suffix, unsigned arity, sort * const * domain, sort * range); @@ -1987,9 +2001,9 @@ public: bool is_and(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_AND); } bool is_not(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_NOT); } bool is_eq(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_EQ); } + bool is_iff(expr const * n) const { return is_eq(n) && is_bool(to_app(n)->get_arg(0)); } bool is_oeq(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_OEQ); } bool is_distinct(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_DISTINCT); } - bool is_iff(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_IFF); } bool is_xor(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_XOR); } bool is_ite(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_ITE); } bool is_term_ite(expr const * n) const { return is_ite(n) && !is_bool(n); } @@ -2005,7 +2019,7 @@ public: bool is_and(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_AND); } bool is_not(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_NOT); } bool is_eq(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_EQ); } - bool is_iff(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_IFF); } + bool is_iff(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_EQ) && is_bool(d->get_range()); } bool is_xor(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_XOR); } bool is_ite(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_ITE); } bool is_term_ite(func_decl const * d) const { return is_ite(d) && !is_bool(d->get_range()); } @@ -2015,7 +2029,6 @@ public: MATCH_UNARY(is_not); MATCH_BINARY(is_eq); - MATCH_BINARY(is_iff); MATCH_BINARY(is_implies); MATCH_BINARY(is_and); MATCH_BINARY(is_or); @@ -2023,6 +2036,8 @@ public: MATCH_TERNARY(is_and); MATCH_TERNARY(is_or); + bool is_iff(expr const* n, expr*& lhs, expr*& rhs) const { return is_eq(n, lhs, rhs) && is_bool(lhs); } + bool is_ite(expr const* n, expr*& t1, expr*& t2, expr*& t3) const { if (is_ite(n)) { t1 = to_app(n)->get_arg(0); @@ -2035,7 +2050,7 @@ public: public: app * mk_eq(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, get_eq_op(lhs), lhs, rhs); } - app * mk_iff(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_IFF, lhs, rhs); } + app * mk_iff(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_EQ, lhs, rhs); } app * mk_oeq(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_OEQ, lhs, rhs); } app * mk_xor(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_XOR, lhs, rhs); } app * mk_ite(expr * c, expr * t, expr * e) { return mk_app(m_basic_family_id, OP_ITE, c, t, e); } @@ -2151,6 +2166,23 @@ public: return n > 0 && get_sort(p->get_arg(n - 1)) != m_proof_sort; } expr * get_fact(proof const * p) const { SASSERT(is_proof(p)); SASSERT(has_fact(p)); return p->get_arg(p->get_num_args() - 1); } + + class proof_parents { + ast_manager& m; + proof * m_proof; + public: + proof_parents(ast_manager& m, proof * p): m(m), m_proof(p) {} + proof * const * begin() const { return (proof* const*)(m_proof->begin()); } + proof * const * end() const { + unsigned n = m_proof->get_num_args(); + return (proof* const*)(begin() + (m.has_fact(m_proof) ? n - 1 : n)); + } + }; + + proof_parents get_parents(proof* p) { + return proof_parents(*this, p); + } + unsigned get_num_parents(proof const * p) const { SASSERT(is_proof(p)); unsigned n = p->get_num_args(); diff --git a/src/ast/ast_pp_dot.cpp b/src/ast/ast_pp_dot.cpp index 1dfbe9aae..47da4d4f4 100644 --- a/src/ast/ast_pp_dot.cpp +++ b/src/ast/ast_pp_dot.cpp @@ -9,7 +9,8 @@ Abstract: Pretty-printer for proofs in Graphviz format #include "ast/ast_pp_dot.h" // string escaping for DOT -std::string escape_dot(std::string const & s) { +std::string escape_dot(const std::string &s) +{ std::string res; res.reserve(s.size()); // preallocate for (auto c : s) { diff --git a/src/ast/ast_pp_dot.h b/src/ast/ast_pp_dot.h index 537754e83..d233c4be1 100644 --- a/src/ast/ast_pp_dot.h +++ b/src/ast/ast_pp_dot.h @@ -4,10 +4,11 @@ Abstract: Pretty-printer for proofs in Graphviz format --*/ -#pragma once +#ifndef _AST_PP_DOT_ +#define _AST_PP_DOT_ #include -#include "ast_pp.h" +#include "ast/ast_pp.h" class ast_pp_dot { ast_manager & m_manager; @@ -21,4 +22,8 @@ class ast_pp_dot { ast_manager & get_manager() const { return m_manager; } }; -std::ostream &operator<<(std::ostream &out, const ast_pp_dot & p); \ No newline at end of file +std::string escape_dot(std::string const & s); + +std::ostream &operator<<(std::ostream &out, const ast_pp_dot & p); + +#endif /* AST_PP_DOT */ diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index a2958e3c9..9d27ffb24 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -63,10 +63,6 @@ format * smt2_pp_environment::pp_fdecl_name(func_decl * f, unsigned & len) const len = 3; return mk_string(m, "ite"); } - else if (m.is_iff(f)) { - len = 1; - return mk_string(m, "="); - } else { symbol s = f->get_name(); return pp_fdecl_name(s, len, f->is_skolem()); diff --git a/src/ast/ast_smt_pp.cpp b/src/ast/ast_smt_pp.cpp index 635899d23..22adf42d9 100644 --- a/src/ast/ast_smt_pp.cpp +++ b/src/ast/ast_smt_pp.cpp @@ -225,9 +225,6 @@ class smt_printer { else if (m_manager.is_ite(d)) { m_out << "ite"; } - else if (m_manager.is_iff(d)) { - m_out << "="; - } else if (m_manager.is_implies(d)) { m_out << "=>"; } diff --git a/src/ast/ast_util.cpp b/src/ast/ast_util.cpp index 92a539d88..42c0d698b 100644 --- a/src/ast/ast_util.cpp +++ b/src/ast/ast_util.cpp @@ -98,7 +98,7 @@ bool is_atom(ast_manager & m, expr * n) { return true; SASSERT(is_app(n)); if (to_app(n)->get_family_id() != m.get_basic_family_id()) { - return true; + return true; } // the other operators of the basic family are not considered atomic: distinct, ite, and, or, iff, xor, not, implies. return (m.is_eq(n) && !m.is_bool(to_app(n)->get_arg(0))) || m.is_true(n) || m.is_false(n); @@ -106,7 +106,7 @@ bool is_atom(ast_manager & m, expr * n) { bool is_literal(ast_manager & m, expr * n) { - return + return is_atom(m, n) || (m.is_not(n) && is_atom(m, to_app(n)->get_arg(0))); } @@ -187,7 +187,7 @@ expr * mk_not(ast_manager & m, expr * arg) { expr * atom; if (m.is_not(arg, atom)) return atom; - else if (m.is_true(arg)) + else if (m.is_true(arg)) return m.mk_false(); else if (m.is_false(arg)) return m.mk_true(); @@ -195,6 +195,11 @@ expr * mk_not(ast_manager & m, expr * arg) { return m.mk_not(arg); } +expr_ref mk_not(const expr_ref& e) { + return expr_ref(mk_not(e.m(), e), e.m()); +} + + expr_ref push_not(const expr_ref& e) { ast_manager& m = e.get_manager(); if (!is_app(e)) { @@ -221,7 +226,7 @@ expr_ref push_not(const expr_ref& e) { } return mk_and(args); } - return expr_ref(mk_not(m, e), m); + return expr_ref(mk_not(m, e), m); } expr * expand_distinct(ast_manager & m, unsigned num_args, expr * const * args) { @@ -277,7 +282,7 @@ void flatten_and(expr_ref_vector& result) { } result[i] = result.back(); result.pop_back(); - --i; + --i; } else if (m.is_not(result[i].get(), e1) && m.is_implies(e1,e2,e3)) { result.push_back(e2); @@ -289,7 +294,7 @@ void flatten_and(expr_ref_vector& result) { m.is_false(e1))) { result[i] = result.back(); result.pop_back(); - --i; + --i; } else if (m.is_false(result[i].get()) || (m.is_not(result[i].get(), e1) && @@ -303,10 +308,17 @@ void flatten_and(expr_ref_vector& result) { void flatten_and(expr* fml, expr_ref_vector& result) { SASSERT(result.get_manager().is_bool(fml)); - result.push_back(fml); + result.push_back(fml); flatten_and(result); } +void flatten_and(expr_ref& fml) { + expr_ref_vector fmls(fml.get_manager()); + fmls.push_back(fml); + flatten_and(fmls); + fml = mk_and(fmls); +} + void flatten_or(expr_ref_vector& result) { ast_manager& m = result.get_manager(); expr* e1, *e2, *e3; @@ -333,7 +345,7 @@ void flatten_or(expr_ref_vector& result) { } result[i] = result.back(); result.pop_back(); - --i; + --i; } else if (m.is_implies(result[i].get(),e2,e3)) { result.push_back(e3); @@ -345,7 +357,7 @@ void flatten_or(expr_ref_vector& result) { m.is_true(e1))) { result[i] = result.back(); result.pop_back(); - --i; + --i; } else if (m.is_true(result[i].get()) || (m.is_not(result[i].get(), e1) && @@ -354,12 +366,12 @@ void flatten_or(expr_ref_vector& result) { result.push_back(m.mk_true()); return; } - } + } } void flatten_or(expr* fml, expr_ref_vector& result) { SASSERT(result.get_manager().is_bool(fml)); - result.push_back(fml); + result.push_back(fml); flatten_or(result); } diff --git a/src/ast/ast_util.h b/src/ast/ast_util.h index 446854f5e..23c2205bb 100644 --- a/src/ast/ast_util.h +++ b/src/ast/ast_util.h @@ -127,6 +127,8 @@ inline expr_ref mk_or(expr_ref_vector const& args) { return expr_ref(mk_or(args. */ expr * mk_not(ast_manager & m, expr * arg); +expr_ref mk_not(const expr_ref& e); + /** Negate and push over conjunction or disjunction. */ @@ -150,6 +152,8 @@ expr_ref mk_distinct(expr_ref_vector const& args); void flatten_and(expr_ref_vector& result); +void flatten_and(expr_ref& fml); + void flatten_and(expr* fml, expr_ref_vector& result); void flatten_or(expr_ref_vector& result); @@ -158,4 +162,3 @@ void flatten_or(expr* fml, expr_ref_vector& result); #endif /* AST_UTIL_H_ */ - diff --git a/src/ast/datatype_decl_plugin.h b/src/ast/datatype_decl_plugin.h index 88b50af82..17602efcf 100644 --- a/src/ast/datatype_decl_plugin.h +++ b/src/ast/datatype_decl_plugin.h @@ -358,8 +358,10 @@ namespace datatype { bool is_accessor(func_decl * f) const { return is_decl_of(f, m_family_id, OP_DT_ACCESSOR); } bool is_update_field(func_decl * f) const { return is_decl_of(f, m_family_id, OP_DT_UPDATE_FIELD); } bool is_constructor(app * f) const { return is_app_of(f, m_family_id, OP_DT_CONSTRUCTOR); } + bool is_constructor(expr* e) const { return is_app(e) && is_constructor(to_app(e)); } bool is_recognizer0(app * f) const { return is_app_of(f, m_family_id, OP_DT_RECOGNISER);} bool is_is(app * f) const { return is_app_of(f, m_family_id, OP_DT_IS);} + bool is_is(expr * e) const { return is_app(e) && is_is(to_app(e)); } bool is_recognizer(app * f) const { return is_recognizer0(f) || is_is(f); } bool is_accessor(app * f) const { return is_app_of(f, m_family_id, OP_DT_ACCESSOR); } bool is_update_field(app * f) const { return is_app_of(f, m_family_id, OP_DT_UPDATE_FIELD); } diff --git a/src/ast/for_each_expr.h b/src/ast/for_each_expr.h index 50a4089dc..6e203ccee 100644 --- a/src/ast/for_each_expr.h +++ b/src/ast/for_each_expr.h @@ -128,6 +128,20 @@ void for_each_expr(ForEachProc & proc, expr * n) { for_each_expr_core(proc, visited, n); } +template +void for_each_expr(ForEachProc & proc, unsigned n, expr * const* es) { + expr_mark visited; + for (unsigned i = 0; i < n; ++i) + for_each_expr_core(proc, visited, es[i]); +} + +template +void for_each_expr(ForEachProc & proc, expr_ref_vector const& es) { + expr_mark visited; + for (expr* e : es) + for_each_expr_core(proc, visited, e); +} + template void quick_for_each_expr(ForEachProc & proc, expr_fast_mark1 & visited, expr * n) { for_each_expr_core(proc, visited, n); diff --git a/src/ast/macro_substitution.cpp b/src/ast/macro_substitution.cpp index a51e1a066..2868a1876 100644 --- a/src/ast/macro_substitution.cpp +++ b/src/ast/macro_substitution.cpp @@ -77,7 +77,7 @@ void macro_substitution::cleanup() { void macro_substitution::insert(func_decl * f, quantifier * q, proof * pr, expr_dependency * dep) { DEBUG_CODE({ app * body = to_app(q->get_expr()); - SASSERT(m_manager.is_eq(body) || m_manager.is_iff(body)); + SASSERT(m_manager.is_eq(body)); expr * lhs = body->get_arg(0); expr * rhs = body->get_arg(1); SASSERT(is_app_of(lhs, f) || is_app_of(rhs, f)); @@ -146,7 +146,7 @@ void macro_substitution::erase(func_decl * f) { void macro_substitution::get_head_def(quantifier * q, func_decl * f, app * & head, expr * & def) { app * body = to_app(q->get_expr()); - SASSERT(m_manager.is_eq(body) || m_manager.is_iff(body)); + SASSERT(m_manager.is_eq(body)); expr * lhs = to_app(body)->get_arg(0); expr * rhs = to_app(body)->get_arg(1); SASSERT(is_app_of(lhs, f) || is_app_of(rhs, f)); diff --git a/src/ast/macros/macro_manager.cpp b/src/ast/macros/macro_manager.cpp index 71bdac0a4..2f429ccf7 100644 --- a/src/ast/macros/macro_manager.cpp +++ b/src/ast/macros/macro_manager.cpp @@ -169,9 +169,8 @@ void macro_manager::mark_forbidden(unsigned n, justified_expr const * exprs) { void macro_manager::get_head_def(quantifier * q, func_decl * d, app * & head, expr * & def) const { app * body = to_app(q->get_expr()); - SASSERT(m.is_eq(body) || m.is_iff(body)); - expr * lhs = to_app(body)->get_arg(0); - expr * rhs = to_app(body)->get_arg(1); + expr * lhs = nullptr, *rhs = nullptr; + VERIFY(m.is_eq(body, lhs, rhs)); SASSERT(is_app_of(lhs, d) || is_app_of(rhs, d)); SASSERT(!is_app_of(lhs, d) || !is_app_of(rhs, d)); if (is_app_of(lhs, d)) { diff --git a/src/ast/macros/macro_util.cpp b/src/ast/macros/macro_util.cpp index 77290c95f..b2f31e374 100644 --- a/src/ast/macros/macro_util.cpp +++ b/src/ast/macros/macro_util.cpp @@ -175,7 +175,7 @@ bool macro_util::is_macro_head(expr * n, unsigned num_decls) const { */ bool macro_util::is_left_simple_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def) const { - if (m_manager.is_eq(n) || m_manager.is_iff(n)) { + if (m_manager.is_eq(n)) { expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); if (is_macro_head(lhs, num_decls) && !is_forbidden(to_app(lhs)->get_decl()) && @@ -207,7 +207,7 @@ bool macro_util::is_left_simple_macro(expr * n, unsigned num_decls, app_ref & he */ bool macro_util::is_right_simple_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def) const { - if (m_manager.is_eq(n) || m_manager.is_iff(n)) { + if (m_manager.is_eq(n)) { expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); if (is_macro_head(rhs, num_decls) && !is_forbidden(to_app(rhs)->get_decl()) && @@ -339,10 +339,9 @@ bool macro_util::is_pseudo_predicate_macro(expr * n, app_ref & head, app_ref & t TRACE("macro_util", tout << "processing: " << mk_pp(n, m_manager) << "\n";); expr * body = to_quantifier(n)->get_expr(); unsigned num_decls = to_quantifier(n)->get_num_decls(); - if (!m_manager.is_iff(body)) + expr * lhs, *rhs; + if (!m_manager.is_iff(body, lhs, rhs)) return false; - expr * lhs = to_app(body)->get_arg(0); - expr * rhs = to_app(body)->get_arg(1); if (is_pseudo_head(lhs, num_decls, head, t) && !is_forbidden(head->get_decl()) && !occurs(head->get_decl(), rhs)) { diff --git a/src/ast/macros/quasi_macros.cpp b/src/ast/macros/quasi_macros.cpp index a308b5b17..3a0735e25 100644 --- a/src/ast/macros/quasi_macros.cpp +++ b/src/ast/macros/quasi_macros.cpp @@ -158,7 +158,7 @@ bool quasi_macros::is_quasi_macro(expr * e, app_ref & a, expr_ref & t) const { if (is_quantifier(e) && to_quantifier(e)->is_forall()) { quantifier * q = to_quantifier(e); expr * qe = q->get_expr(); - if ((m_manager.is_eq(qe) || m_manager.is_iff(qe))) { + if ((m_manager.is_eq(qe))) { expr * lhs = to_app(qe)->get_arg(0); expr * rhs = to_app(qe)->get_arg(1); diff --git a/src/ast/normal_forms/nnf.cpp b/src/ast/normal_forms/nnf.cpp index 11461680d..c7f20ced6 100644 --- a/src/ast/normal_forms/nnf.cpp +++ b/src/ast/normal_forms/nnf.cpp @@ -582,7 +582,7 @@ struct nnf::imp { return true; } - bool is_eq(app * t) const { return m().is_eq(t) || m().is_iff(t); } + bool is_eq(app * t) const { return m().is_eq(t); } bool process_iff_xor(app * t, frame & fr) { SASSERT(t->get_num_args() == 2); @@ -630,7 +630,7 @@ struct nnf::imp { } bool process_eq(app * t, frame & fr) { - if (m().is_bool(t->get_arg(0))) + if (m().is_iff(t)) return process_iff_xor(t, fr); else return process_default(t, fr); @@ -725,7 +725,6 @@ struct nnf::imp { return process_implies(t, fr); case OP_ITE: return process_ite(t, fr); - case OP_IFF: case OP_XOR: return process_iff_xor(t, fr); case OP_EQ: diff --git a/src/ast/proofs/proof_checker.cpp b/src/ast/proofs/proof_checker.cpp index 0e16abd11..e148299be 100644 --- a/src/ast/proofs/proof_checker.cpp +++ b/src/ast/proofs/proof_checker.cpp @@ -1,4 +1,3 @@ - /*++ Copyright (c) 2015 Microsoft Corporation @@ -12,11 +11,11 @@ Copyright (c) 2015 Microsoft Corporation #include "ast/rewriter/th_rewriter.h" #include "ast/rewriter/var_subst.h" -#define IS_EQUIV(_e_) (m.is_eq(_e_) || m.is_iff(_e_)) +#define IS_EQUIV(_e_) m.is_eq(_e_) #define SAME_OP(_d1_, _d2_) ((_d1_ == _d2_) || (IS_EQUIV(_d1_) && IS_EQUIV(_d2_))) -proof_checker::hyp_decl_plugin::hyp_decl_plugin() : +proof_checker::hyp_decl_plugin::hyp_decl_plugin() : m_cons(nullptr), m_atom(nullptr), m_nil(nullptr), @@ -59,13 +58,13 @@ func_decl * proof_checker::hyp_decl_plugin::mk_func_decl(decl_kind k) { } func_decl * proof_checker::hyp_decl_plugin::mk_func_decl( - decl_kind k, unsigned num_parameters, parameter const * parameters, + decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort * range) { return mk_func_decl(k); } func_decl * proof_checker::hyp_decl_plugin::mk_func_decl( - decl_kind k, unsigned num_parameters, parameter const * parameters, + decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned num_args, expr * const * args, sort * range) { return mk_func_decl(k); } @@ -84,8 +83,8 @@ void proof_checker::hyp_decl_plugin::get_sort_names(svector & sort } } -proof_checker::proof_checker(ast_manager& m) : m(m), m_todo(m), m_marked(), m_pinned(m), m_nil(m), - m_dump_lemmas(false), m_logic("AUFLIA"), m_proof_lemma_id(0) { +proof_checker::proof_checker(ast_manager& m) : m(m), m_todo(m), m_marked(), m_pinned(m), m_nil(m), + m_dump_lemmas(false), m_logic("AUFLIRA"), m_proof_lemma_id(0) { symbol fam_name("proof_hypothesis"); if (!m.has_plugin(fam_name)) { m.register_plugin(fam_name, alloc(hyp_decl_plugin)); @@ -98,16 +97,16 @@ proof_checker::proof_checker(ast_manager& m) : m(m), m_todo(m), m_marked(), m_pi bool proof_checker::check(proof* p, expr_ref_vector& side_conditions) { proof_ref curr(m); m_todo.push_back(p); - + bool result = true; while (result && !m_todo.empty()) { curr = m_todo.back(); m_todo.pop_back(); result = check1(curr.get(), side_conditions); if (!result) { - IF_VERBOSE(0, ast_ll_pp(verbose_stream() << "Proof check failed\n", m, curr.get());); + IF_VERBOSE(0, ast_ll_pp(verbose_stream() << "Proof check failed\n", m, curr.get());); UNREACHABLE(); - } + } } m_hypotheses.reset(); @@ -157,10 +156,10 @@ bool proof_checker::check1_spc(proof* p, expr_ref_vector& side_conditions) { } return false; } - case PR_SPC_REWRITE: - case PR_SUPERPOSITION: + case PR_SPC_REWRITE: + case PR_SUPERPOSITION: case PR_EQUALITY_RESOLUTION: - case PR_SPC_RESOLUTION: + case PR_SPC_RESOLUTION: case PR_FACTORING: case PR_SPC_DER: { if (match_fact(p, fact)) { @@ -176,7 +175,7 @@ bool proof_checker::check1_spc(proof* p, expr_ref_vector& side_conditions) { side_conditions.push_back(rewrite_cond.get()); return true; } - return false; + return false; } default: UNREACHABLE(); @@ -201,7 +200,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { func_decl_ref f1(m), f2(m); expr_ref_vector terms1(m), terms2(m), terms(m); sort_ref_vector decls1(m), decls2(m); - + if (match_proof(p, proofs)) { for (unsigned i = 0; i < proofs.size(); ++i) { add_premise(proofs.get(i)); @@ -211,6 +210,8 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { switch(k) { case PR_UNDEF: return true; + case PR_TRUE: + return true; case PR_ASSERTED: return true; case PR_GOAL: @@ -229,8 +230,8 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { return false; } case PR_REFLEXIVITY: { - if (match_fact(p, fact) && - match_proof(p) && + if (match_fact(p, fact) && + match_proof(p) && (match_equiv(fact, t1, t2) || match_oeq(fact, t1, t2)) && (t1.get() == t2.get())) { return true; @@ -243,7 +244,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { match_proof(p, p1) && match_fact(p1.get(), fml) && match_binary(fact.get(), d1, l1, r1) && - match_binary(fml.get(), d2, l2, r2) && + match_binary(fml.get(), d2, l2, r2) && SAME_OP(d1.get(), d2.get()) && l1.get() == r2.get() && r1.get() == l2.get()) { @@ -271,7 +272,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } UNREACHABLE(); return false; - } + } case PR_TRANSITIVITY_STAR: { if (match_fact(p, fact) && match_binary(fact.get(), d1, t1, t2)) { @@ -292,14 +293,14 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { return false; } } - return + return vertices.size() == 2 && vertices.contains(t1->get_id()) && vertices.contains(t2->get_id()); } UNREACHABLE(); return false; - } + } case PR_MONOTONICITY: { TRACE("proof_checker", tout << mk_bounded_pp(p, m, 3) << "\n";); if (match_fact(p, fact) && @@ -315,7 +316,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { if (term1 != term2) { bool found = false; for(unsigned j = 0; j < proofs.size() && !found; ++j) { - found = + found = match_fact(proofs[j].get(), fml) && match_binary(fml.get(), d2, s1, s2) && SAME_OP(d1.get(), d2.get()) && @@ -351,7 +352,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { for (unsigned i = 0; i < q1->get_num_decls(); ++i) { if (q1->get_decl_sort(i) != q2->get_decl_sort(i)) { // term is not well-typed. - UNREACHABLE(); + UNREACHABLE(); return false; } } @@ -408,7 +409,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { side_conditions.push_back(fact.get()); return true; } - IF_VERBOSE(0, verbose_stream() << "Expected proof of equality:\n" << mk_bounded_pp(p, m);); + IF_VERBOSE(0, verbose_stream() << "Expected proof of equality:\n" << mk_bounded_pp(p, m);); return false; } case PR_REWRITE_STAR: { @@ -426,8 +427,8 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { side_conditions.push_back(rewrite_cond.get()); return true; } - IF_VERBOSE(0, verbose_stream() << "Expected proof of equality:\n" << mk_bounded_pp(p, m);); - return false; + IF_VERBOSE(0, verbose_stream() << "Expected proof of equality:\n" << mk_bounded_pp(p, m);); + return false; } case PR_PULL_QUANT: { if (match_proof(p) && @@ -437,7 +438,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { // TBD: check the enchilada. return true; } - IF_VERBOSE(0, verbose_stream() << "Expected proof of equivalence with a quantifier:\n" << mk_bounded_pp(p, m);); + IF_VERBOSE(0, verbose_stream() << "Expected proof of equivalence with a quantifier:\n" << mk_bounded_pp(p, m);); return false; } case PR_PUSH_QUANT: { @@ -457,7 +458,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } else { return false; - } + } } } UNREACHABLE(); @@ -468,7 +469,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { match_fact(p, fact) && match_iff(fact.get(), t1, t2)) { // TBD: - // match_quantifier(t1.get(), is_forall1, decls1, body1) + // match_quantifier(t1.get(), is_forall1, decls1, body1) // filter out decls1 that occur in body1. // if list is empty, then t2 could be just body1. // otherwise t2 is also a quantifier. @@ -487,7 +488,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { // TBD: check that terms are set of equalities. // t2 is an instance of a predicate in terms1 return true; - } + } IF_VERBOSE(0, verbose_stream() << "does not match last rule: " << mk_pp(p, m) << "\n";); return false; } @@ -509,7 +510,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { get_hypotheses(p1.get(), hypotheses); if (hypotheses.size() == 1 && match_negated(hypotheses.get(0), fact)) { // Suppose fact is (or a b c) and hypothesis is (not (or a b c)) - // That is, (or a b c) should be viewed as a 'quoted expression' and a unary clause, + // That is, (or a b c) should be viewed as a 'quoted expression' and a unary clause, // instead of a clause with three literals. return true; } @@ -533,7 +534,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { }); UNREACHABLE(); return false; - } + } TRACE("proof_checker", tout << "Matched:\n"; ast_ll_pp(tout, m, hypotheses[i].get()); ast_ll_pp(tout, m, ors[j-1].get());); @@ -548,7 +549,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { proofs.size() == 2 && match_fact(proofs[0].get(), fml1) && match_fact(proofs[1].get(), fml2) && - match_negated(fml1.get(), fml2.get()) && + m.is_complement(fml1.get(), fml2.get()) && m.is_false(fact.get())) { return true; } @@ -562,7 +563,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } bool found = false; for (unsigned j = 0; !found && j < terms1.size(); ++j) { - if (match_negated(terms1.get(j), fml2)) { + if (m.is_complement(terms1.get(j), fml2)) { found = true; if (j + 1 < terms1.size()) { terms1[j] = terms1.get(terms1.size()-1); @@ -571,7 +572,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } } if (!found) { - TRACE("pr_unit_bug", + TRACE("pr_unit_bug", tout << "Parents:\n"; for (unsigned i = 0; i < proofs.size(); i++) { expr_ref p(m); @@ -590,9 +591,9 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } } switch(terms1.size()) { - case 0: + case 0: return m.is_false(fact.get()); - case 1: + case 1: return fact.get() == terms1[0].get(); default: { if (match_or(fact.get(), terms2)) { @@ -615,7 +616,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { return false; } - + } } UNREACHABLE(); @@ -633,7 +634,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } UNREACHABLE(); return false; - } + } case PR_IFF_FALSE: { // iff_false(?rule(?p1, (not ?fml)), (iff ?fml false)) if (match_proof(p, p1) && @@ -676,11 +677,11 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { } case PR_DEF_INTRO: { // def_intro(?fml) - // + // // ?fml: forall x . ~p(x) or e(x) and forall x . ~e(x) or p(x) // : forall x . ~cond(x) or f(x) = then(x) and forall x . cond(x) or f(x) = else(x) // : forall x . f(x) = e(x) - // + // if (match_fact(p, fact) && match_proof(p) && m.is_bool(fact.get())) { @@ -710,7 +711,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { return true; } UNREACHABLE(); - return false; + return false; } case PR_NNF_POS: { // TBD: @@ -733,9 +734,9 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { is_forall = true; } if (is_quantifier(e)) { - q = to_quantifier(e); + q = to_quantifier(e); // TBD check that quantifier is properly instantiated - return is_forall == q->is_forall(); + return is_forall == q->is_forall(); } } UNREACHABLE(); @@ -786,7 +787,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { vs(premise, sub.size(), sub.c_ptr(), premise); } fmls.push_back(premise.get()); - TRACE("proof_checker", + TRACE("proof_checker", tout << mk_pp(premise.get(), m) << "\n"; for (unsigned j = 0; j < sub.size(); ++j) { tout << mk_pp(sub[j], m) << " "; @@ -808,7 +809,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { // ok } else { - IF_VERBOSE(0, verbose_stream() << "Could not establish complementarity for:\n" << + IF_VERBOSE(0, verbose_stream() << "Could not establish complementarity for:\n" << mk_pp(lit1, m) << "\n" << mk_pp(lit2, m) << "\n" << mk_pp(p, m) << "\n";); } fmls[i] = premise1; @@ -823,7 +824,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { else { premise0 = m.mk_iff(premise0, conclusion); } - side_conditions.push_back(premise0); + side_conditions.push_back(premise0); return true; } default: @@ -839,7 +840,7 @@ bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { (=> (and ln+1 ln+2 .. ln+m) l0) or in the most general (ground) form: (=> (and ln+1 ln+2 .. ln+m) (or l0 l1 .. ln-1)) - In other words we use the following (Prolog style) convention for Horn + In other words we use the following (Prolog style) convention for Horn implications: The head of a Horn implication is position 0, the first conjunct in the body of an implication is position 1 @@ -851,7 +852,7 @@ void proof_checker::set_false(expr_ref& e, unsigned position, expr_ref& lit) { app* a = to_app(e); expr* head, *body; expr_ref_vector args(m); - if (m.is_or(e)) { + if (m.is_or(e)) { SASSERT(position < a->get_num_args()); args.append(a->get_num_args(), a->get_args()); lit = args[position].get(); @@ -863,13 +864,13 @@ void proof_checker::set_false(expr_ref& e, unsigned position, expr_ref& lit) { unsigned num_heads = 1; if (m.is_or(head)) { num_heads = to_app(head)->get_num_args(); - heads = to_app(head)->get_args(); + heads = to_app(head)->get_args(); } expr*const* bodies = &body; unsigned num_bodies = 1; if (m.is_and(body)) { num_bodies = to_app(body)->get_num_args(); - bodies = to_app(body)->get_args(); + bodies = to_app(body)->get_args(); } if (position < num_heads) { args.append(num_heads, heads); @@ -882,7 +883,7 @@ void proof_checker::set_false(expr_ref& e, unsigned position, expr_ref& lit) { args.append(num_bodies, bodies); lit = m.mk_not(args[position].get()); args[position] = m.mk_true(); - e = m.mk_implies(m.mk_and(args.size(), args.c_ptr()), head); + e = m.mk_implies(m.mk_and(args.size(), args.c_ptr()), head); } } else if (position == 0) { @@ -912,7 +913,7 @@ void proof_checker::add_premise(proof* p) { } bool proof_checker::match_proof(proof const* p) const { - return + return m.is_proof(p) && m.get_num_parents(p) == 0; } @@ -925,7 +926,7 @@ bool proof_checker::match_proof(proof const* p, proof_ref& p0) const { } return false; } - + bool proof_checker::match_proof(proof const* p, proof_ref& p0, proof_ref& p1) const { if (m.is_proof(p) && m.get_num_parents(p) == 2) { @@ -1032,7 +1033,7 @@ bool proof_checker::match_and(expr const* e, expr_ref_vector& terms) const { } bool proof_checker::match_iff(expr const* e, expr_ref& t1, expr_ref& t2) const { - return match_op(e, OP_IFF, t1, t2); + return match_op(e, OP_EQ, t1, t2) && m.is_bool(t1); } bool proof_checker::match_equiv(expr const* e, expr_ref& t1, expr_ref& t2) const { @@ -1044,7 +1045,7 @@ bool proof_checker::match_implies(expr const* e, expr_ref& t1, expr_ref& t2) con } bool proof_checker::match_eq(expr const* e, expr_ref& t1, expr_ref& t2) const { - return match_op(e, OP_EQ, t1, t2) || match_iff(e, t1, t2); + return match_op(e, OP_EQ, t1, t2); } bool proof_checker::match_oeq(expr const* e, expr_ref& t1, expr_ref& t2) const { @@ -1053,7 +1054,7 @@ bool proof_checker::match_oeq(expr const* e, expr_ref& t1, expr_ref& t2) const { bool proof_checker::match_negated(expr const* a, expr* b) const { expr_ref t(m); - return + return (match_not(a, t) && t.get() == b) || (match_not(b, t) && t.get() == a); } @@ -1080,7 +1081,7 @@ void proof_checker::get_hypotheses(proof* p, expr_ref_vector& ante) { p = stack.back(); SASSERT(m.is_proof(p)); if (m_hypotheses.contains(p)) { - stack.pop_back(); + stack.pop_back(); continue; } if (is_hypothesis(p) && match_fact(p, hyp)) { @@ -1122,13 +1123,13 @@ void proof_checker::get_hypotheses(proof* p, expr_ref_vector& ante) { if (!m_hypotheses.find(p, h)) { UNREACHABLE(); } - + ptr_buffer hyps; ptr_buffer todo; expr_mark mark; todo.push_back(h); expr_ref a(m), b(m); - + while (!todo.empty()) { h = todo.back(); @@ -1153,14 +1154,14 @@ void proof_checker::get_hypotheses(proof* p, expr_ref_vector& ante) { ast_ll_pp(tout << "Getting hypotheses from: ", m, p); tout << "Found hypotheses:\n"; for (unsigned i = 0; i < ante.size(); ++i) { - ast_ll_pp(tout, m, ante[i].get()); + ast_ll_pp(tout, m, ante[i].get()); } }); } bool proof_checker::match_nil(expr const* e) const { - return + return is_app(e) && to_app(e)->get_family_id() == m_hyp_fid && to_app(e)->get_decl_kind() == OP_NIL; @@ -1201,7 +1202,7 @@ expr* proof_checker::mk_nil() { } bool proof_checker::is_hypothesis(proof const* p) const { - return + return m.is_proof(p) && p->get_decl_kind() == PR_HYPOTHESIS; } @@ -1223,7 +1224,7 @@ expr* proof_checker::mk_hyp(unsigned num_hyps, expr * const * hyps) { } else { return result; - } + } } void proof_checker::dump_proof(proof const* pr) { @@ -1244,9 +1245,9 @@ void proof_checker::dump_proof(proof const* pr) { void proof_checker::dump_proof(unsigned num_antecedents, expr * const * antecedents, expr * consequent) { char buffer[128]; #ifdef _WINDOWS - sprintf_s(buffer, ARRAYSIZE(buffer), "proof_lemma_%d.smt", m_proof_lemma_id); + sprintf_s(buffer, ARRAYSIZE(buffer), "proof_lemma_%d.smt2", m_proof_lemma_id); #else - sprintf(buffer, "proof_lemma_%d.smt", m_proof_lemma_id); + sprintf(buffer, "proof_lemma_%d.smt2", m_proof_lemma_id); #endif std::ofstream out(buffer); ast_smt_pp pp(m); @@ -1265,7 +1266,7 @@ void proof_checker::dump_proof(unsigned num_antecedents, expr * const * antecede bool proof_checker::check_arith_literal(bool is_pos, app* lit0, rational const& coeff, expr_ref& sum, bool& is_strict) { arith_util a(m); app* lit = lit0; - + if (m.is_not(lit)) { lit = to_app(lit->get_arg(0)); is_pos = !is_pos; @@ -1277,6 +1278,10 @@ bool proof_checker::check_arith_literal(bool is_pos, app* lit0, rational const& SASSERT(lit->get_num_args() == 2); sort* s = m.get_sort(lit->get_arg(0)); bool is_int = a.is_int(s); + if (!is_int && a.is_int_expr(lit->get_arg(0))) { + is_int = true; + s = a.mk_int(); + } if (!is_int && is_pos && (a.is_gt(lit) || a.is_lt(lit))) { is_strict = true; @@ -1305,25 +1310,25 @@ bool proof_checker::check_arith_literal(bool is_pos, app* lit0, rational const& } // - // Multiplying by coefficients over strict + // Multiplying by coefficients over strict // and non-strict inequalities: // // (a <= b) * 2 // (a - b <= 0) * 2 // (2a - 2b <= 0) - + // (a < b) * 2 <=> // (a +1 <= b) * 2 <=> // 2a + 2 <= 2b <=> // 2a+2-2b <= 0 - + bool strict_ineq = is_pos?(a.is_gt(lit) || a.is_lt(lit)):(a.is_ge(lit) || a.is_le(lit)); - + if (is_int && strict_ineq) { sum = a.mk_add(sum, sign1); } - + term = a.mk_mul(sign1, a0); sum = a.mk_add(sum, term); term = a.mk_mul(sign2, a1); @@ -1355,7 +1360,7 @@ bool proof_checker::check_arith_proof(proof* p) { } expr_ref fact(m); proof_ref_vector proofs(m); - + if (!match_fact(p, fact)) { UNREACHABLE(); return false; @@ -1384,7 +1389,7 @@ bool proof_checker::check_arith_proof(proof* p) { coeffs[i] = lc*coeffs[i]; } } - + unsigned num_parents = m.get_num_parents(p); for (unsigned i = 0; i < num_parents; i++) { proof * a = m.get_parent(p, i); @@ -1393,11 +1398,10 @@ bool proof_checker::check_arith_proof(proof* p) { return false; } } - - if (m.is_or(fact)) { + if (m.is_or(fact)) { app* disj = to_app(fact); unsigned num_args = disj->get_num_args(); - for (unsigned i = 0; i < num_args; ++i) { + for (unsigned i = 0; i < num_args; ++i) { app* lit = to_app(disj->get_arg(i)); if (!check_arith_literal(false, lit, coeffs[offset++], sum, is_strict)) { return false; @@ -1409,13 +1413,13 @@ bool proof_checker::check_arith_proof(proof* p) { return false; } } - + if (!sum.get()) { return false; } - + sort* s = m.get_sort(sum); - + if (is_strict) { sum = autil.mk_lt(sum, autil.mk_numeral(rational(0), s)); @@ -1433,6 +1437,6 @@ bool proof_checker::check_arith_proof(proof* p) { dump_proof(p); return false; } - + return true; } diff --git a/src/ast/proofs/proof_utils.h b/src/ast/proofs/proof_utils.h index 455f39c4f..729a30eb0 100644 --- a/src/ast/proofs/proof_utils.h +++ b/src/ast/proofs/proof_utils.h @@ -110,10 +110,10 @@ public: if (m.is_or(decl)) { mk_or_core(args, res); } - else if (m.is_iff(decl) && args.size() == 2) + else if (m.is_eq(decl) && args.size() == 2) // avoiding simplifying equalities. In particular, // we don't want (= (not a) (not b)) to be reduced to (= a b) - { res = m.mk_iff(args.get(0), args.get(1)); } + { res = m.mk_eq(args.get(0), args.get(1)); } else { brwr.mk_app(decl, args.size(), args.c_ptr(), res); } } diff --git a/src/ast/rewriter/CMakeLists.txt b/src/ast/rewriter/CMakeLists.txt index f030e2704..9d80fd5ac 100644 --- a/src/ast/rewriter/CMakeLists.txt +++ b/src/ast/rewriter/CMakeLists.txt @@ -16,6 +16,7 @@ z3_add_component(rewriter enum2bv_rewriter.cpp expr_replacer.cpp expr_safe_replace.cpp + factor_equivs.cpp factor_rewriter.cpp fpa_rewriter.cpp inj_axiom.cpp diff --git a/src/ast/rewriter/arith_rewriter.cpp b/src/ast/rewriter/arith_rewriter.cpp index 9824884a4..379020331 100644 --- a/src/ast/rewriter/arith_rewriter.cpp +++ b/src/ast/rewriter/arith_rewriter.cpp @@ -800,52 +800,105 @@ br_status arith_rewriter::mk_idiv_core(expr * arg1, expr * arg2, expr_ref & resu result = m_util.mk_numeral(div(v1, v2), is_int); return BR_DONE; } - expr_ref quot(m()); - if (divides(arg1, arg2, quot)) { - result = m_util.mk_mul(quot, m_util.mk_idiv(arg1, arg1)); - return BR_REWRITE2; + if (m_util.is_numeral(arg2, v2, is_int) && v2.is_one()) { + result = arg1; + return BR_DONE; + } + if (m_util.is_numeral(arg2, v2, is_int) && v2.is_zero()) { + return BR_FAILED; + } + if (arg1 == arg2) { + expr_ref zero(m_util.mk_int(0), m()); + result = m().mk_ite(m().mk_eq(arg1, zero), m_util.mk_idiv(zero, zero), m_util.mk_int(1)); + return BR_REWRITE3; + } + if (divides(arg1, arg2, result)) { + return BR_REWRITE_FULL; } return BR_FAILED; } -bool arith_rewriter::divides(expr* d, expr* n, expr_ref& quot) { - if (d == n) { - quot = m_util.mk_numeral(rational(1), m_util.is_int(d)); + +// +// implement div ab ac = floor( ab / ac) = floor (b / c) = div b c +// +bool arith_rewriter::divides(expr* num, expr* den, expr_ref& result) { + expr_fast_mark1 mark; + rational num_r(1), den_r(1); + expr* num_e = nullptr, *den_e = nullptr; + ptr_buffer args1, args2; + flat_mul(num, args1); + flat_mul(den, args2); + for (expr * arg : args1) { + mark.mark(arg); + if (m_util.is_numeral(arg, num_r)) num_e = arg; + } + for (expr* arg : args2) { + if (mark.is_marked(arg)) { + result = remove_divisor(arg, num, den); + return true; + } + if (m_util.is_numeral(arg, den_r)) den_e = arg; + } + rational g = gcd(num_r, den_r); + if (!g.is_one()) { + SASSERT(g.is_pos()); + // replace num_e, den_e by their gcd reduction. + for (unsigned i = 0; i < args1.size(); ++i) { + if (args1[i] == num_e) { + args1[i] = m_util.mk_numeral(num_r / g, true); + break; + } + } + for (unsigned i = 0; i < args2.size(); ++i) { + if (args2[i] == den_e) { + args2[i] = m_util.mk_numeral(den_r / g, true); + break; + } + } + + num = m_util.mk_mul(args1.size(), args1.c_ptr()); + den = m_util.mk_mul(args2.size(), args2.c_ptr()); + result = m_util.mk_idiv(num, den); return true; } - if (m_util.is_mul(n)) { - expr_ref_vector muls(m()); - muls.push_back(n); - expr* n1, *n2; - rational r1, r2; - for (unsigned i = 0; i < muls.size(); ++i) { - if (m_util.is_mul(muls[i].get(), n1, n2)) { - muls[i] = n1; - muls.push_back(n2); - --i; - } - } - if (m_util.is_numeral(d, r1) && !r1.is_zero()) { - for (unsigned i = 0; i < muls.size(); ++i) { - if (m_util.is_numeral(muls[i].get(), r2) && (r2 / r1).is_int()) { - muls[i] = m_util.mk_numeral(r2 / r1, m_util.is_int(d)); - quot = m_util.mk_mul(muls.size(), muls.c_ptr()); - return true; - } - } - } - else { - for (unsigned i = 0; i < muls.size(); ++i) { - if (d == muls[i].get()) { - muls[i] = muls.back(); - muls.pop_back(); - quot = m_util.mk_mul(muls.size(), muls.c_ptr()); - return true; - } - } + return false; +} + +expr_ref arith_rewriter::remove_divisor(expr* arg, expr* num, expr* den) { + ptr_buffer args1, args2; + flat_mul(num, args1); + flat_mul(den, args2); + remove_divisor(arg, args1); + remove_divisor(arg, args2); + expr_ref zero(m_util.mk_int(0), m()); + num = args1.empty() ? m_util.mk_int(1) : m_util.mk_mul(args1.size(), args1.c_ptr()); + den = args2.empty() ? m_util.mk_int(1) : m_util.mk_mul(args2.size(), args2.c_ptr()); + return expr_ref(m().mk_ite(m().mk_eq(zero, arg), m_util.mk_idiv(zero, zero), m_util.mk_idiv(num, den)), m()); +} + +void arith_rewriter::flat_mul(expr* e, ptr_buffer& args) { + args.push_back(e); + for (unsigned i = 0; i < args.size(); ++i) { + e = args[i]; + if (m_util.is_mul(e)) { + args.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + args[i] = args.back(); + args.shrink(args.size()-1); + --i; + } + } +} + +void arith_rewriter::remove_divisor(expr* d, ptr_buffer& args) { + for (unsigned i = 0; i < args.size(); ++i) { + if (args[i] == d) { + args[i] = args.back(); + args.shrink(args.size()-1); + return; } } - return false; + UNREACHABLE(); } br_status arith_rewriter::mk_mod_core(expr * arg1, expr * arg2, expr_ref & result) { diff --git a/src/ast/rewriter/arith_rewriter.h b/src/ast/rewriter/arith_rewriter.h index 65fbfb43f..fa8677941 100644 --- a/src/ast/rewriter/arith_rewriter.h +++ b/src/ast/rewriter/arith_rewriter.h @@ -95,7 +95,10 @@ class arith_rewriter : public poly_rewriter { expr_ref neg_monomial(expr * e) const; expr * mk_sin_value(rational const & k); app * mk_sqrt(rational const & k); - bool divides(expr* d, expr* n, expr_ref& quot); + bool divides(expr* d, expr* n, expr_ref& result); + expr_ref remove_divisor(expr* arg, expr* num, expr* den); + void flat_mul(expr* e, ptr_buffer& args); + void remove_divisor(expr* d, ptr_buffer& args); public: arith_rewriter(ast_manager & m, params_ref const & p = params_ref()): diff --git a/src/ast/rewriter/bool_rewriter.cpp b/src/ast/rewriter/bool_rewriter.cpp index 5cf25335b..46613a12e 100644 --- a/src/ast/rewriter/bool_rewriter.cpp +++ b/src/ast/rewriter/bool_rewriter.cpp @@ -39,7 +39,6 @@ br_status bool_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * co SASSERT(f->get_family_id() == m().get_basic_family_id()); switch (f->get_decl_kind()) { case OP_EQ: - case OP_IFF: SASSERT(num_args == 2); return mk_eq_core(args[0], args[1], result); case OP_DISTINCT: @@ -428,7 +427,7 @@ bool bool_rewriter::simp_nested_eq_ite(expr * t, expr_fast_mark1 & neg_lits, exp neg = true; t = to_app(t)->get_arg(0); } - if (m().is_iff(t) || m().is_eq(t)) { + if (m().is_eq(t)) { bool modified = false; expr * new_lhs = simp_arg(to_app(t)->get_arg(0), neg_lits, pos_lits, modified); expr * new_rhs = simp_arg(to_app(t)->get_arg(1), neg_lits, pos_lits, modified); @@ -708,7 +707,7 @@ br_status bool_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) { expr *la, *lb, *ra, *rb; // fold (iff (iff a b) (iff (not a) b)) to false - if (m().is_iff(lhs, la, lb) && m().is_iff(rhs, ra, rb)) { + if (m().is_eq(lhs, la, lb) && m().is_eq(rhs, ra, rb)) { expr *n; if ((la == ra && ((m().is_not(rb, n) && n == lb) || (m().is_not(lb, n) && n == rb))) || diff --git a/src/ast/rewriter/bool_rewriter.h b/src/ast/rewriter/bool_rewriter.h index 34e03c1ed..83ece2aae 100644 --- a/src/ast/rewriter/bool_rewriter.h +++ b/src/ast/rewriter/bool_rewriter.h @@ -81,7 +81,7 @@ public: bool_rewriter(ast_manager & m, params_ref const & p = params_ref()):m_manager(m), m_local_ctx_cost(0) { updt_params(p); } ast_manager & m() const { return m_manager; } family_id get_fid() const { return m().get_basic_family_id(); } - bool is_eq(expr * t) const { return m().is_eq(t) || m().is_iff(t); } + bool is_eq(expr * t) const { return m().is_eq(t); } bool flat() const { return m_flat; } void set_flat(bool f) { m_flat = f; } diff --git a/src/ast/rewriter/der.cpp b/src/ast/rewriter/der.cpp index 021943585..54409e9c2 100644 --- a/src/ast/rewriter/der.cpp +++ b/src/ast/rewriter/der.cpp @@ -40,11 +40,8 @@ static bool is_neg_var(ast_manager & m, expr * e, unsigned num_decls) { */ bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { // (not (= VAR t)) and (not (iff VAR t)) cases - if (m_manager.is_not(e) && (m_manager.is_eq(to_app(e)->get_arg(0)) || m_manager.is_iff(to_app(e)->get_arg(0)))) { - app * eq = to_app(to_app(e)->get_arg(0)); - SASSERT(m_manager.is_eq(eq) || m_manager.is_iff(eq)); - expr * lhs = eq->get_arg(0); - expr * rhs = eq->get_arg(1); + expr *eq, * lhs, *rhs; + if (m_manager.is_not(e, eq) && m_manager.is_eq(eq, lhs, rhs)) { if (!is_var(lhs, num_decls) && !is_var(rhs, num_decls)) return false; if (!is_var(lhs, num_decls)) @@ -60,9 +57,7 @@ bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { return true; } // (iff VAR t) and (iff (not VAR) t) cases - else if (m_manager.is_iff(e)) { - expr * lhs = to_app(e)->get_arg(0); - expr * rhs = to_app(e)->get_arg(1); + else if (m_manager.is_eq(e, lhs, rhs) && m_manager.is_bool(lhs)) { // (iff VAR t) case if (is_var(lhs, num_decls) || is_var(rhs, num_decls)) { if (!is_var(lhs, num_decls)) diff --git a/src/ast/factor_equivs.cpp b/src/ast/rewriter/factor_equivs.cpp similarity index 68% rename from src/ast/factor_equivs.cpp rename to src/ast/rewriter/factor_equivs.cpp index c33b3a18e..6384ad8ce 100644 --- a/src/ast/factor_equivs.cpp +++ b/src/ast/rewriter/factor_equivs.cpp @@ -25,8 +25,11 @@ Revision History: */ -#include "ast/factor_equivs.h" #include "ast/arith_decl_plugin.h" +#include "ast/for_each_expr.h" +#include "ast/ast_pp.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "ast/rewriter/factor_equivs.h" /** Factors input vector v into equivalence classes and the rest @@ -59,8 +62,8 @@ void factor_eqs(expr_ref_vector &v, expr_equiv_class &equiv) { equiv.merge(e1, e2); } else { - if (j < i) { - v[j] = v.get(i); + if (j < i) { + v[j] = v.get(i); } j++; } @@ -68,19 +71,52 @@ void factor_eqs(expr_ref_vector &v, expr_equiv_class &equiv) { v.shrink(j); } +/** + * Chooses a representative of an equivalence class + */ +expr *choose_rep(expr_equiv_class::eq_class &clazz, ast_manager &m) { + expr *rep = nullptr; + unsigned rep_sz, elem_sz; + for (expr *elem : clazz) { + if (!m.is_value(elem)) { + elem_sz = get_num_exprs(elem); + if (!rep || (rep && rep_sz > elem_sz)) { + rep = elem; + rep_sz = elem_sz; + } + } + } + TRACE("equiv", + tout << "Rep: " << mk_pp(rep, m) << "\n"; + for (expr *el : clazz) + tout << mk_pp(el, m) << "\n"; + tout << "RepEnd\n";); + + return rep; +} + +void rewrite_eqs (expr_ref_vector &v, expr_equiv_class &equiv) { + ast_manager &m = v.m(); + expr_safe_replace sub(m); + for (auto eq_class : equiv) { + expr *rep = choose_rep(eq_class, m); + for (expr *el : eq_class) { + if (el != rep) { + sub.insert (el, rep); + } + } + } + sub(v); +} + + /** * converts equivalence classes to equalities */ void equiv_to_expr(expr_equiv_class &equiv, expr_ref_vector &out) { ast_manager &m = out.get_manager(); for (auto eq_class : equiv) { - expr *rep = nullptr; - for (expr *elem : eq_class) { - if (!m.is_value(elem)) { - rep = elem; - break; - } - } + expr *rep = choose_rep(eq_class, m); SASSERT(rep); for (expr *elem : eq_class) { if (rep != elem) { diff --git a/src/ast/factor_equivs.h b/src/ast/rewriter/factor_equivs.h similarity index 96% rename from src/ast/factor_equivs.h rename to src/ast/rewriter/factor_equivs.h index f0ce1608d..5d306bad8 100644 --- a/src/ast/factor_equivs.h +++ b/src/ast/rewriter/factor_equivs.h @@ -60,6 +60,8 @@ public: obj_equiv_class(Manager& m) : m_to_obj(m) {} + Manager &m() const {return m_to_obj.m();} + void add_elem(OBJ*o) { SASSERT(!m_to_int.find(o)); add_elem_impl(o); @@ -169,6 +171,11 @@ typedef obj_equiv_class expr_equiv_class; * Factors input vector v into equivalence classes and the rest */ void factor_eqs(expr_ref_vector &v, expr_equiv_class &equiv); +/** + * Rewrite expressions in v by choosing a representative from the + * equivalence class. + */ +void rewrite_eqs(expr_ref_vector &v, expr_equiv_class &equiv); /** * converts equivalence classes to equalities */ diff --git a/src/ast/rewriter/quant_hoist.cpp b/src/ast/rewriter/quant_hoist.cpp index 3592f84cd..2f1116299 100644 --- a/src/ast/rewriter/quant_hoist.cpp +++ b/src/ast/rewriter/quant_hoist.cpp @@ -259,7 +259,7 @@ private: result = m.mk_ite(t1, tt2, tt3); } } - else if ((m.is_eq(fml, t1, t2) && m.is_bool(t1)) || m.is_iff(fml, t1, t2)) { + else if (m.is_eq(fml, t1, t2) && m.is_bool(t1)) { expr_ref tt1(m), tt2(m), ntt1(m), ntt2(m), nt1(m), nt2(m); pull_quantifier(t1, qt, vars, tt1, use_fresh, rewrite_ok); pull_quantifier(t2, qt, vars, tt2, use_fresh, rewrite_ok); diff --git a/src/ast/rewriter/rewriter.h b/src/ast/rewriter/rewriter.h index 8c00f541b..c761b5ca3 100644 --- a/src/ast/rewriter/rewriter.h +++ b/src/ast/rewriter/rewriter.h @@ -346,6 +346,8 @@ public: void set_bindings(unsigned num_bindings, expr * const * bindings); void set_inv_bindings(unsigned num_bindings, expr * const * bindings); + void update_binding_at(unsigned i, expr* binding); + void update_inv_binding_at(unsigned i, expr* binding); void operator()(expr * t, expr_ref & result, proof_ref & result_pr); void operator()(expr * t, expr_ref & result) { operator()(t, result, m_pr); } void operator()(expr * n, unsigned num_bindings, expr * const * bindings, expr_ref & result) { diff --git a/src/ast/rewriter/rewriter_def.h b/src/ast/rewriter/rewriter_def.h index 4d4c4f708..e8a14b953 100644 --- a/src/ast/rewriter/rewriter_def.h +++ b/src/ast/rewriter/rewriter_def.h @@ -639,6 +639,17 @@ void rewriter_tpl::set_inv_bindings(unsigned num_bindings, expr * const TRACE("rewriter", display_bindings(tout);); } +template +void rewriter_tpl::update_inv_binding_at(unsigned i, expr* binding) { + m_bindings[i] = binding; +} + +template +void rewriter_tpl::update_binding_at(unsigned i, expr* binding) { + m_bindings[m_bindings.size() - i - 1] = binding; +} + + template template void rewriter_tpl::main_loop(expr * t, expr_ref & result, proof_ref & result_pr) { diff --git a/src/ast/rewriter/seq_rewriter.cpp b/src/ast/rewriter/seq_rewriter.cpp index 3ead59833..8f7b56381 100644 --- a/src/ast/rewriter/seq_rewriter.cpp +++ b/src/ast/rewriter/seq_rewriter.cpp @@ -1206,8 +1206,7 @@ bool seq_rewriter::is_sequence(expr* e, expr_ref_vector& seq) { e = todo.back(); todo.pop_back(); if (m_util.str.is_string(e, s)) { - for (unsigned i = s.length(); i > 0; ) { - --i; + for (unsigned i = 0; i < s.length(); ++i) { seq.push_back(m_util.str.mk_char(s, i)); } } @@ -1218,14 +1217,13 @@ bool seq_rewriter::is_sequence(expr* e, expr_ref_vector& seq) { seq.push_back(e1); } else if (m_util.str.is_concat(e, e1, e2)) { - todo.push_back(e1); todo.push_back(e2); + todo.push_back(e1); } else { return false; } } - seq.reverse(); return true; } diff --git a/src/ast/rewriter/th_rewriter.cpp b/src/ast/rewriter/th_rewriter.cpp index f5a0ff0f7..912df0fc9 100644 --- a/src/ast/rewriter/th_rewriter.cpp +++ b/src/ast/rewriter/th_rewriter.cpp @@ -187,7 +187,7 @@ struct th_rewriter_cfg : public default_rewriter_cfg { if (st != BR_FAILED) return st; } - if (k == OP_EQ || k == OP_IFF) { + if (k == OP_EQ) { SASSERT(num == 2); st = apply_tamagotchi(args[0], args[1], result); if (st != BR_FAILED) diff --git a/src/ast/rewriter/var_subst.cpp b/src/ast/rewriter/var_subst.cpp index 97e0ddb19..7877cf1d2 100644 --- a/src/ast/rewriter/var_subst.cpp +++ b/src/ast/rewriter/var_subst.cpp @@ -24,6 +24,10 @@ Notes: #include "ast/for_each_expr.h" void var_subst::operator()(expr * n, unsigned num_args, expr * const * args, expr_ref & result) { + if (is_ground(n)) { + result = n; + return; + } SASSERT(is_well_sorted(result.m(), n)); m_reducer.reset(); if (m_std_order) diff --git a/src/ast/static_features.cpp b/src/ast/static_features.cpp index 9f52ea9a5..e3530b4b5 100644 --- a/src/ast/static_features.cpp +++ b/src/ast/static_features.cpp @@ -81,6 +81,7 @@ void static_features::reset() { m_has_str = false; m_has_seq_non_str = false; m_has_arrays = false; + m_has_ext_arrays = false; m_arith_k_sum .reset(); m_num_arith_terms = 0; m_num_arith_eqs = 0; @@ -153,8 +154,10 @@ bool static_features::is_diff_atom(expr const * e) const { bool static_features::is_gate(expr const * e) const { if (is_basic_expr(e)) { switch (to_app(e)->get_decl_kind()) { - case OP_ITE: case OP_AND: case OP_OR: case OP_IFF: case OP_XOR: case OP_IMPLIES: + case OP_ITE: case OP_AND: case OP_OR: case OP_XOR: case OP_IMPLIES: return true; + case OP_EQ: + return m_manager.is_bool(e); } } return false; @@ -206,7 +209,7 @@ void static_features::update_core(expr * e) { case OP_OR: m_num_ors++; break; - case OP_IFF: + case OP_EQ: m_num_iffs++; break; } @@ -271,8 +274,11 @@ void static_features::update_core(expr * e) { m_has_bv = true; if (!m_has_fpa && (m_fpautil.is_float(e) || m_fpautil.is_rm(e))) m_has_fpa = true; - if (!m_has_arrays && m_arrayutil.is_array(e)) + if (!m_has_arrays && m_arrayutil.is_array(e)) m_has_arrays = true; + if (!m_has_ext_arrays && m_arrayutil.is_array(e) && + !m_arrayutil.is_select(e) && !m_arrayutil.is_store(e)) + m_has_ext_arrays = true; if (!m_has_str && m_sequtil.str.is_string_term(e)) m_has_str = true; if (!m_has_seq_non_str && m_sequtil.str.is_non_string_sequence(e)) { @@ -414,7 +420,7 @@ void static_features::process(expr * e, bool form_ctx, bool or_and_ctx, bool ite form_ctx_new = true; or_and_ctx_new = true; break; - case OP_IFF: + case OP_EQ: form_ctx_new = true; break; } diff --git a/src/ast/static_features.h b/src/ast/static_features.h index 5473ba0ff..197947026 100644 --- a/src/ast/static_features.h +++ b/src/ast/static_features.h @@ -82,6 +82,7 @@ struct static_features { bool m_has_str; // has String-typed terms bool m_has_seq_non_str; // has non-String-typed Sequence terms bool m_has_arrays; // + bool m_has_ext_arrays; // does this use extended array theory. rational m_arith_k_sum; // sum of the numerals in arith atoms. unsigned m_num_arith_terms; unsigned m_num_arith_eqs; // equalities of the form t = k where k is a numeral diff --git a/src/cmd_context/basic_cmds.cpp b/src/cmd_context/basic_cmds.cpp index 1d8fdb3de..846fb1ded 100644 --- a/src/cmd_context/basic_cmds.cpp +++ b/src/cmd_context/basic_cmds.cpp @@ -138,8 +138,8 @@ ATOMIC_CMD(get_assignment_cmd, "get-assignment", "retrieve assignment", { macro_decls const & _m = kv.m_value; for (auto md : _m) { if (md.m_domain.size() == 0 && ctx.m().is_bool(md.m_body)) { - expr_ref val(ctx.m()); - m->eval(md.m_body, val, true); + model::scoped_model_completion _scm(*m, true); + expr_ref val = (*m)(md.m_body); if (ctx.m().is_true(val) || ctx.m().is_false(val)) { if (first) first = false; @@ -223,7 +223,7 @@ ATOMIC_CMD(get_proof_graph_cmd, "get-proof-graph", "retrieve proof and print it }); static void print_core(cmd_context& ctx) { - ptr_vector core; + expr_ref_vector core(ctx.m()); ctx.get_check_sat_result()->get_unsat_core(core); ctx.regular_stream() << "("; bool first = true; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 0bb4b091e..6dd7fbcaa 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -1826,8 +1826,9 @@ void cmd_context::validate_model() { catch (contains_underspecified_op_proc::found) { continue; } - TRACE("model_validate", model_smt2_pp(tout, *this, *(md.get()), 0);); + TRACE("model_validate", model_smt2_pp(tout, *this, *md, 0);); IF_VERBOSE(10, verbose_stream() << "model check failed on: " << mk_pp(a, m()) << "\n";); + IF_VERBOSE(11, model_smt2_pp(verbose_stream(), *this, *md, 0);); invalid_model = true; } } diff --git a/src/cmd_context/extra_cmds/dbg_cmds.cpp b/src/cmd_context/extra_cmds/dbg_cmds.cpp index fa1d56fe2..64eeac42e 100644 --- a/src/cmd_context/extra_cmds/dbg_cmds.cpp +++ b/src/cmd_context/extra_cmds/dbg_cmds.cpp @@ -29,7 +29,11 @@ Notes: #include "tactic/arith/bound_manager.h" #include "ast/used_vars.h" #include "ast/rewriter/var_subst.h" +#include "ast/ast_util.h" #include "util/gparams.h" +#include "qe/qe_mbp.h" +#include "qe/qe_mbi.h" +#include "qe/qe_term_graph.h" BINARY_SYM_CMD(get_quantifier_body_cmd, @@ -352,6 +356,184 @@ public: void execute(cmd_context & ctx) override { ctx.display_dimacs(); } }; +class mbp_cmd : public cmd { + expr* m_fml; + ptr_vector m_vars; +public: + mbp_cmd():cmd("mbp") {} + char const * get_usage() const override { return " ()"; } + char const * get_descr(cmd_context & ctx) const override { return "perform model based projection"; } + unsigned get_arity() const override { return 2; } + cmd_arg_kind next_arg_kind(cmd_context& ctx) const override { + if (m_fml == nullptr) return CPK_EXPR; + return CPK_EXPR_LIST; + } + void set_next_arg(cmd_context& ctx, expr * arg) override { m_fml = arg; } + void set_next_arg(cmd_context & ctx, unsigned num, expr * const * ts) override { + m_vars.append(num, ts); + } + void prepare(cmd_context & ctx) override { m_fml = nullptr; m_vars.reset(); } + void execute(cmd_context & ctx) override { + ast_manager& m = ctx.m(); + app_ref_vector vars(m); + model_ref mdl; + if (!ctx.is_model_available(mdl) || !ctx.get_check_sat_result()) { + throw cmd_exception("model is not available"); + } + for (expr* v : m_vars) { + if (!is_uninterp_const(v)) { + throw cmd_exception("invalid variable argument. Uninterpreted variable expected"); + } + vars.push_back(to_app(v)); + } + qe::mbp mbp(m); + expr_ref fml(m_fml, m); + mbp.spacer(vars, *mdl.get(), fml); + ctx.regular_stream() << fml << "\n"; + } +}; + +class mbi_cmd : public cmd { + expr* m_a; + expr* m_b; + ptr_vector m_vars; +public: + mbi_cmd():cmd("mbi") {} + char const * get_usage() const override { return " (vars)"; } + char const * get_descr(cmd_context & ctx) const override { return "perform model based interpolation"; } + unsigned get_arity() const override { return 3; } + cmd_arg_kind next_arg_kind(cmd_context& ctx) const override { + if (m_a == nullptr) return CPK_EXPR; + if (m_b == nullptr) return CPK_EXPR; + return CPK_FUNC_DECL_LIST; + } + void set_next_arg(cmd_context& ctx, expr * arg) override { + if (m_a == nullptr) + m_a = arg; + else + m_b = arg; + } + void set_next_arg(cmd_context & ctx, unsigned num, func_decl * const * ts) override { + m_vars.append(num, ts); + } + void prepare(cmd_context & ctx) override { m_a = nullptr; m_b = nullptr; m_vars.reset(); } + void execute(cmd_context & ctx) override { + ast_manager& m = ctx.m(); + func_decl_ref_vector vars(m); + for (func_decl* v : m_vars) { + vars.push_back(v); + } + qe::interpolator mbi(m); + expr_ref a(m_a, m); + expr_ref b(m_b, m); + expr_ref itp(m); + solver_factory& sf = ctx.get_solver_factory(); + params_ref p; + solver_ref sA = sf(m, p, false /* no proofs */, true, true, symbol::null); + solver_ref sB = sf(m, p, false /* no proofs */, true, true, symbol::null); + sA->assert_expr(a); + sB->assert_expr(b); + qe::prop_mbi_plugin pA(sA.get()); + qe::prop_mbi_plugin pB(sB.get()); + pA.set_shared(vars); + pB.set_shared(vars); + lbool res = mbi.pingpong(pA, pB, itp); + ctx.regular_stream() << res << " " << itp << "\n"; + } +}; + + +class eufi_cmd : public cmd { + expr* m_a; + expr* m_b; + ptr_vector m_vars; +public: + eufi_cmd():cmd("eufi") {} + char const * get_usage() const override { return " (vars)"; } + char const * get_descr(cmd_context & ctx) const override { return "perform model based interpolation"; } + unsigned get_arity() const override { return 3; } + cmd_arg_kind next_arg_kind(cmd_context& ctx) const override { + if (m_a == nullptr) return CPK_EXPR; + if (m_b == nullptr) return CPK_EXPR; + return CPK_FUNC_DECL_LIST; + } + void set_next_arg(cmd_context& ctx, expr * arg) override { + if (m_a == nullptr) + m_a = arg; + else + m_b = arg; + } + void set_next_arg(cmd_context & ctx, unsigned num, func_decl * const * ts) override { + m_vars.append(num, ts); + } + void prepare(cmd_context & ctx) override { m_a = nullptr; m_b = nullptr; m_vars.reset(); } + void execute(cmd_context & ctx) override { + ast_manager& m = ctx.m(); + func_decl_ref_vector vars(m); + for (func_decl* v : m_vars) { + vars.push_back(v); + } + qe::interpolator mbi(m); + expr_ref a(m_a, m); + expr_ref b(m_b, m); + expr_ref itp(m); + solver_factory& sf = ctx.get_solver_factory(); + params_ref p; + solver_ref sA = sf(m, p, false /* no proofs */, true, true, symbol::null); + solver_ref sB = sf(m, p, false /* no proofs */, true, true, symbol::null); + solver_ref sNotA = sf(m, p, false /* no proofs */, true, true, symbol::null); + solver_ref sNotB = sf(m, p, false /* no proofs */, true, true, symbol::null); + sA->assert_expr(a); + sNotA->assert_expr(m.mk_not(a)); + sB->assert_expr(b); + sNotB->assert_expr(m.mk_not(b)); + qe::euf_arith_mbi_plugin pA(sA.get(), sNotA.get()); + qe::euf_arith_mbi_plugin pB(sB.get(), sNotB.get()); + pA.set_shared(vars); + pB.set_shared(vars); + lbool res = mbi.pogo(pA, pB, itp); + ctx.regular_stream() << res << " " << itp << "\n"; + } +}; + + +class euf_project_cmd : public cmd { + unsigned m_arg_index; + ptr_vector m_lits; + ptr_vector m_vars; +public: + euf_project_cmd():cmd("euf-project") {} + char const * get_usage() const override { return "(exprs) (vars)"; } + char const * get_descr(cmd_context & ctx) const override { return "perform congruence projection"; } + unsigned get_arity() const override { return 2; } + cmd_arg_kind next_arg_kind(cmd_context& ctx) const override { + if (m_arg_index == 0) return CPK_EXPR_LIST; + return CPK_FUNC_DECL_LIST; + } + void set_next_arg(cmd_context& ctx, unsigned num, expr * const* args) override { + m_lits.append(num, args); + m_arg_index = 1; + } + void set_next_arg(cmd_context & ctx, unsigned num, func_decl * const * ts) override { + m_vars.append(num, ts); + } + void prepare(cmd_context & ctx) override { m_arg_index = 0; m_lits.reset(); m_vars.reset(); } + void execute(cmd_context & ctx) override { + ast_manager& m = ctx.m(); + func_decl_ref_vector vars(m); + expr_ref_vector lits(m); + for (func_decl* v : m_vars) vars.push_back(v); + for (expr* e : m_lits) lits.push_back(e); + flatten_and(lits); + qe::term_graph tg(m); + tg.set_vars(vars, false); + tg.add_lits(lits); + expr_ref_vector p = tg.project(); + ctx.regular_stream() << p << "\n"; + } + +}; + void install_dbg_cmds(cmd_context & ctx) { ctx.insert(alloc(print_dimacs_cmd)); @@ -377,4 +559,8 @@ void install_dbg_cmds(cmd_context & ctx) { ctx.insert(alloc(instantiate_cmd)); ctx.insert(alloc(instantiate_nested_cmd)); ctx.insert(alloc(set_next_id)); + ctx.insert(alloc(mbp_cmd)); + ctx.insert(alloc(mbi_cmd)); + ctx.insert(alloc(euf_project_cmd)); + ctx.insert(alloc(eufi_cmd)); } diff --git a/src/math/simplex/model_based_opt.cpp b/src/math/simplex/model_based_opt.cpp index f12918bfe..09f6d5c2d 100644 --- a/src/math/simplex/model_based_opt.cpp +++ b/src/math/simplex/model_based_opt.cpp @@ -35,11 +35,114 @@ std::ostream& operator<<(std::ostream& out, opt::ineq_type ie) { namespace opt { + /** + * Convert a row ax + coeffs + coeff = value into a definition for x + * x = (value - coeffs - coeff)/a + * as backdrop we have existing assignments to x and other variables that + * satisfy the equality with value, and such that value satisfies + * the row constraint ( = , <= , < , mod) + */ + model_based_opt::def::def(row const& r, unsigned x) { + for (var const & v : r.m_vars) { + if (v.m_id != x) { + m_vars.push_back(v); + } + else { + m_div = -v.m_coeff; + } + } + m_coeff = r.m_coeff; + if (r.m_type == opt::t_lt) m_coeff += m_div; + normalize(); + SASSERT(m_div.is_pos()); + } + + model_based_opt::def model_based_opt::def::operator+(def const& other) const { + def result; + vector const& vs1 = m_vars; + vector const& vs2 = other.m_vars; + vector & vs = result.m_vars; + rational c1(1), c2(1); + if (m_div != other.m_div) { + c1 = other.m_div; + c2 = m_div; + } + unsigned i = 0, j = 0; + while (i < vs1.size() || j < vs2.size()) { + unsigned v1 = UINT_MAX, v2 = UINT_MAX; + if (i < vs1.size()) v1 = vs1[i].m_id; + if (j < vs2.size()) v2 = vs2[j].m_id; + if (v1 == v2) { + vs.push_back(vs1[i]); + vs.back().m_coeff *= c1; + vs.back().m_coeff += c2 * vs2[j].m_coeff; + ++i; ++j; + if (vs.back().m_coeff.is_zero()) { + vs.pop_back(); + } + } + else if (v1 < v2) { + vs.push_back(vs1[i]); + vs.back().m_coeff *= c1; + } + else { + vs.push_back(vs2[j]); + vs.back().m_coeff *= c2; + } + } + result.m_div = c1*m_div; + result.m_coeff = (m_coeff*c1) + (other.m_coeff*c2); + result.normalize(); + return result; + } + + model_based_opt::def model_based_opt::def::operator/(rational const& r) const { + def result(*this); + result.m_div *= r; + result.normalize(); + return result; + } + + model_based_opt::def model_based_opt::def::operator*(rational const& n) const { + def result(*this); + for (var& v : result.m_vars) { + v.m_coeff *= n; + } + result.m_coeff *= n; + result.normalize(); + return result; + } + + model_based_opt::def model_based_opt::def::operator+(rational const& n) const { + def result(*this); + result.m_coeff += n * result.m_div; + result.normalize(); + return result; + } + + void model_based_opt::def::normalize() { + if (m_div.is_one()) return; + rational g(m_div); + g = gcd(g, m_coeff); + for (var const& v : m_vars) { + g = gcd(g, abs(v.m_coeff)); + if (g.is_one()) break; + } + if (m_div.is_neg()) { + g.neg(); + } + if (!g.is_one()) { + for (var& v : m_vars) { + v.m_coeff /= g; + } + m_coeff /= g; + m_div /= g; + } + } model_based_opt::model_based_opt() { m_rows.push_back(row()); } - bool model_based_opt::invariant() { for (unsigned i = 0; i < m_rows.size(); ++i) { @@ -61,7 +164,7 @@ namespace opt { PASSERT(index == 0 || m_var2row_ids[vars[i].m_id].contains(index)); } - PASSERT(r.m_value == get_row_value(r)); + PASSERT(r.m_value == eval(r)); PASSERT(r.m_type != t_eq || r.m_value.is_zero()); // values satisfy constraints PASSERT(index == 0 || r.m_type != t_lt || r.m_value.is_neg()); @@ -105,14 +208,14 @@ namespace opt { if (find_bound(x, bound_row_index, bound_coeff, coeff.is_pos())) { SASSERT(!bound_coeff.is_zero()); TRACE("opt", display(tout << "update: " << v << " ", objective()); - for (unsigned i = 0; i < m_above.size(); ++i) { - display(tout << "resolve: ", m_rows[m_above[i]]); + for (unsigned above : m_above) { + display(tout << "resolve: ", m_rows[above]); }); - for (unsigned i = 0; i < m_above.size(); ++i) { - resolve(bound_row_index, bound_coeff, m_above[i], x); + for (unsigned above : m_above) { + resolve(bound_row_index, bound_coeff, above, x); } - for (unsigned i = 0; i < m_below.size(); ++i) { - resolve(bound_row_index, bound_coeff, m_below[i], x); + for (unsigned below : m_below) { + resolve(bound_row_index, bound_coeff, below, x); } // coeff*x + objective <= ub // a2*x + t2 <= 0 @@ -151,8 +254,7 @@ namespace opt { rational old_val = m_var2value[x]; m_var2value[x] = val; unsigned_vector const& row_ids = m_var2row_ids[x]; - for (unsigned i = 0; i < row_ids.size(); ++i) { - unsigned row_id = row_ids[i]; + for (unsigned row_id : row_ids) { rational coeff = get_coefficient(row_id, x); if (coeff.is_zero()) { continue; @@ -166,8 +268,7 @@ namespace opt { void model_based_opt::update_values(unsigned_vector const& bound_vars, unsigned_vector const& bound_trail) { - for (unsigned i = bound_trail.size(); i > 0; ) { - --i; + for (unsigned i = bound_trail.size(); i-- > 0; ) { unsigned x = bound_vars[i]; row& r = m_rows[bound_trail[i]]; rational val = r.m_coeff; @@ -175,8 +276,7 @@ namespace opt { rational new_x_val; rational x_coeff, eps(0); vector const& vars = r.m_vars; - for (unsigned j = 0; j < vars.size(); ++j) { - var const& v = vars[j]; + for (var const& v : vars) { if (x == v.m_id) { x_coeff = v.m_coeff; } @@ -218,19 +318,17 @@ namespace opt { << " eps: " << eps << " ", r); ); m_var2value[x] = new_x_val; - r.m_value = get_row_value(r); + r.m_value = eval(r); SASSERT(invariant(bound_trail[i], r)); } // update and check bounds for all other affected rows. - for (unsigned i = bound_trail.size(); i > 0; ) { - --i; + for (unsigned i = bound_trail.size(); i-- > 0; ) { unsigned x = bound_vars[i]; unsigned_vector const& row_ids = m_var2row_ids[x]; - for (unsigned j = 0; j < row_ids.size(); ++j) { - unsigned row_id = row_ids[j]; + for (unsigned row_id : row_ids) { row & r = m_rows[row_id]; - r.m_value = get_row_value(r); + r.m_value = eval(r); SASSERT(invariant(row_id, r)); } } @@ -245,8 +343,7 @@ namespace opt { uint_set visited; m_above.reset(); m_below.reset(); - for (unsigned i = 0; i < row_ids.size(); ++i) { - unsigned row_id = row_ids[i]; + for (unsigned row_id : row_ids) { SASSERT(row_id != m_objective_id); if (visited.contains(row_id)) { continue; @@ -289,27 +386,43 @@ namespace opt { m_rows[row_id].m_alive = false; m_retired_rows.push_back(row_id); } + + rational model_based_opt::eval(unsigned x) const { + return m_var2value[x]; + } + + rational model_based_opt::eval(def const& d) const { + vector const& vars = d.m_vars; + rational val = d.m_coeff; + for (var const& v : vars) { + val += v.m_coeff * eval(v.m_id); + } + val /= d.m_div; + return val; + } - rational model_based_opt::get_row_value(row const& r) const { + rational model_based_opt::eval(row const& r) const { vector const& vars = r.m_vars; rational val = r.m_coeff; - for (unsigned i = 0; i < vars.size(); ++i) { - var const& v = vars[i]; - val += v.m_coeff * m_var2value[v.m_id]; + for (var const& v : vars) { + val += v.m_coeff * eval(v.m_id); } return val; } rational model_based_opt::get_coefficient(unsigned row_id, unsigned var_id) const { - row const& r = m_rows[row_id]; - if (r.m_vars.empty()) { + return m_rows[row_id].get_coefficient(var_id); + } + + rational model_based_opt::row::get_coefficient(unsigned var_id) const { + if (m_vars.empty()) { return rational::zero(); } - unsigned lo = 0, hi = r.m_vars.size(); + unsigned lo = 0, hi = m_vars.size(); while (lo < hi) { unsigned mid = lo + (hi - lo)/2; SASSERT(mid < hi); - unsigned id = r.m_vars[mid].m_id; + unsigned id = m_vars[mid].m_id; if (id == var_id) { lo = mid; break; @@ -321,12 +434,12 @@ namespace opt { hi = mid; } } - if (lo == r.m_vars.size()) { + if (lo == m_vars.size()) { return rational::zero(); } - unsigned id = r.m_vars[lo].m_id; + unsigned id = m_vars[lo].m_id; if (id == var_id) { - return r.m_vars[lo].m_coeff; + return m_vars[lo].m_coeff; } else { return rational::zero(); @@ -368,7 +481,7 @@ namespace opt { tout << a1 << " " << a2 << ": "; display(tout, m_rows[row_dst]); display(tout, m_rows[row_src]);); - if (a1.is_pos() != a2.is_pos()) { + if (a1.is_pos() != a2.is_pos() || m_rows[row_src].m_type == opt::t_eq) { mul_add(x, a1, row_src, a2, row_dst); } else { @@ -384,6 +497,17 @@ namespace opt { } } + void model_based_opt::solve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x) { + SASSERT(a1 == get_coefficient(row_src, x)); + SASSERT(a1.is_pos()); + SASSERT(row_src != row_dst); + if (!m_rows[row_dst].m_alive) return; + rational a2 = get_coefficient(row_dst, x); + mul(row_dst, a1); + mul_add(false, row_dst, -a2, row_src); + SASSERT(get_coefficient(row_dst, x).is_zero()); + } + // resolution for integer rows. void model_based_opt::mul_add( unsigned x, rational const& src_c, unsigned row_src, rational const& dst_c, unsigned row_dst) { @@ -400,18 +524,28 @@ namespace opt { rational slack = (abs_src_c - rational::one()) * (abs_dst_c - rational::one()); rational dst_val = dst.m_value - x_val*dst_c; rational src_val = src.m_value - x_val*src_c; - bool use_case1 = - (src_c * dst_val + dst_c * src_val + slack).is_nonpos() - || abs_src_c.is_one() - || abs_dst_c.is_one(); + rational distance = src_c * dst_val + dst_c * src_val + slack; + bool use_case1 = distance.is_nonpos() || abs_src_c.is_one() || abs_dst_c.is_one(); + +#if 0 + if (distance.is_nonpos() && !abs_src_c.is_one() && !abs_dst_c.is_one()) { + unsigned r = copy_row(row_src); + mul_add(false, r, rational::one(), row_dst); + del_var(r, x); + add(r, slack); + TRACE("qe", tout << m_rows[r];); + SASSERT(!m_rows[r].m_value.is_pos()); + } +#endif if (use_case1) { - // dst <- abs_src_c*dst + abs_dst_c*src - slack + TRACE("opt", tout << "slack: " << slack << " " << src_c << " " << dst_val << " " << dst_c << " " << src_val << "\n";); + // dst <- abs_src_c*dst + abs_dst_c*src + slack mul(row_dst, abs_src_c); - sub(row_dst, slack); - mul_add(false, row_dst, abs_dst_c, row_src); + add(row_dst, slack); + mul_add(false, row_dst, abs_dst_c, row_src); return; - } + } // // create finite disjunction for |b|. @@ -432,6 +566,7 @@ namespace opt { // exists z in [0 .. |b|-2] . |b| | (z + s) && a*n_sign(b)(s + z) + |b|t <= 0 // + TRACE("qe", tout << "finite disjunction " << distance << " " << src_c << " " << dst_c << "\n";); vector coeffs; if (abs_dst_c <= abs_src_c) { rational z = mod(dst_val, abs_dst_c); @@ -457,8 +592,8 @@ namespace opt { } void model_based_opt::mk_coeffs_without(vector& dst, vector const src, unsigned x) { - for (unsigned i = 0; i < src.size(); ++i) { - if (src[i].m_id != x) dst.push_back(src[i]); + for (var const & v : src) { + if (v.m_id != x) dst.push_back(v); } } @@ -469,8 +604,8 @@ namespace opt { void model_based_opt::mul(unsigned dst, rational const& c) { if (c.is_one()) return; row& r = m_rows[dst]; - for (unsigned i = 0; i < r.m_vars.size(); ++i) { - r.m_vars[i].m_coeff *= c; + for (auto & v : r.m_vars) { + v.m_coeff *= c; } r.m_coeff *= c; r.m_value *= c; @@ -488,6 +623,21 @@ namespace opt { r.m_value -= c; } + void model_based_opt::del_var(unsigned dst, unsigned x) { + row& r = m_rows[dst]; + unsigned j = 0; + for (var & v : r.m_vars) { + if (v.m_id == x) { + r.m_value -= eval(x)*r.m_coeff; + } + else { + r.m_vars[j++] = v; + } + } + r.m_vars.shrink(j); + } + + void model_based_opt::normalize(unsigned row_id) { row& r = m_rows[row_id]; if (r.m_vars.empty()) return; @@ -529,7 +679,7 @@ namespace opt { row& r1 = m_rows[row_id1]; row const& r2 = m_rows[row_id2]; unsigned i = 0, j = 0; - for(; i < r1.m_vars.size() || j < r2.m_vars.size(); ) { + while (i < r1.m_vars.size() || j < r2.m_vars.size()) { if (j == r2.m_vars.size()) { m_new_vars.append(r1.m_vars.size() - i, r1.m_vars.c_ptr() + i); break; @@ -583,40 +733,59 @@ namespace opt { } void model_based_opt::display(std::ostream& out) const { - for (unsigned i = 0; i < m_rows.size(); ++i) { - display(out, m_rows[i]); + for (auto const& r : m_rows) { + display(out, r); } for (unsigned i = 0; i < m_var2row_ids.size(); ++i) { unsigned_vector const& rows = m_var2row_ids[i]; out << i << ": "; - for (unsigned j = 0; j < rows.size(); ++j) { - out << rows[j] << " "; + for (auto const& r : rows) { + out << r << " "; } out << "\n"; } } - void model_based_opt::display(std::ostream& out, row const& r) const { - vector const& vars = r.m_vars; - out << (r.m_alive?"+":"-") << " "; - for (unsigned i = 0; i < vars.size(); ++i) { - if (i > 0 && vars[i].m_coeff.is_pos()) { + void model_based_opt::display(std::ostream& out, vector const& vars, rational const& coeff) { + unsigned i = 0; + for (var const& v : vars) { + if (i > 0 && v.m_coeff.is_pos()) { out << "+ "; } - out << vars[i].m_coeff << "* v" << vars[i].m_id << " "; + ++i; + if (v.m_coeff.is_one()) { + out << "v" << v.m_id << " "; + } + else { + out << v.m_coeff << "*v" << v.m_id << " "; + } } - if (r.m_coeff.is_pos()) { - out << " + " << r.m_coeff << " "; + if (coeff.is_pos()) { + out << " + " << coeff << " "; } - else if (r.m_coeff.is_neg()) { - out << r.m_coeff << " "; - } + else if (coeff.is_neg()) { + out << coeff << " "; + } + } + + std::ostream& model_based_opt::display(std::ostream& out, row const& r) { + out << (r.m_alive?"+":"-") << " "; + display(out, r.m_vars, r.m_coeff); if (r.m_type == opt::t_mod) { out << r.m_type << " " << r.m_mod << " = 0; value: " << r.m_value << "\n"; } else { out << r.m_type << " 0; value: " << r.m_value << "\n"; } + return out; + } + + std::ostream& model_based_opt::display(std::ostream& out, def const& r) { + display(out, r.m_vars, r.m_coeff); + if (!r.m_div.is_one()) { + out << " / " << r.m_div; + } + return out; } unsigned model_based_opt::add_var(rational const& value, bool is_int) { @@ -638,10 +807,10 @@ namespace opt { r.m_vars.append(coeffs.size(), coeffs.c_ptr()); bool is_int_row = true; std::sort(r.m_vars.begin(), r.m_vars.end(), var::compare()); - for (unsigned i = 0; i < coeffs.size(); ++i) { - val += m_var2value[coeffs[i].m_id] * coeffs[i].m_coeff; - SASSERT(!is_int(coeffs[i].m_id) || coeffs[i].m_coeff.is_int()); - is_int_row &= is_int(coeffs[i].m_id); + for (auto const& c : coeffs) { + val += m_var2value[c.m_id] * c.m_coeff; + SASSERT(!is_int(c.m_id) || c.m_coeff.is_int()); + is_int_row &= is_int(c.m_id); } r.m_alive = true; r.m_coeff = c; @@ -674,8 +843,8 @@ namespace opt { unsigned dst = new_row(); row const& r = m_rows[src]; set_row(dst, r.m_vars, r.m_coeff, r.m_mod, r.m_type); - for (unsigned i = 0; i < r.m_vars.size(); ++i) { - m_var2row_ids[r.m_vars[i].m_id].push_back(dst); + for (auto const& v : r.m_vars) { + m_var2row_ids[v.m_id].push_back(dst); } SASSERT(invariant(dst, m_rows[dst])); return dst; @@ -692,8 +861,8 @@ namespace opt { void model_based_opt::add_constraint(vector const& coeffs, rational const& c, rational const& m, ineq_type rel) { unsigned row_id = new_row(); set_row(row_id, coeffs, c, m, rel); - for (unsigned i = 0; i < coeffs.size(); ++i) { - m_var2row_ids[coeffs[i].m_id].push_back(row_id); + for (var const& coeff : coeffs) { + m_var2row_ids[coeff.m_id].push_back(row_id); } SASSERT(invariant(row_id, m_rows[row_id])); } @@ -703,9 +872,9 @@ namespace opt { } void model_based_opt::get_live_rows(vector& rows) { - for (unsigned i = 0; i < m_rows.size(); ++i) { - if (m_rows[i].m_alive) { - rows.push_back(m_rows[i]); + for (row const& r : m_rows) { + if (r.m_alive) { + rows.push_back(r); } } } @@ -728,7 +897,7 @@ namespace opt { // t0 <= s for each s (M inequalities). // If N >= M the construction is symmetric. // - void model_based_opt::project(unsigned x) { + model_based_opt::def model_based_opt::project(unsigned x, bool compute_def) { unsigned_vector& lub_rows = m_lub; unsigned_vector& glb_rows = m_glb; unsigned_vector& mod_rows = m_mod; @@ -742,9 +911,9 @@ namespace opt { glb_rows.reset(); mod_rows.reset(); bool lub_is_unit = false, glb_is_unit = false; + unsigned eq_row = UINT_MAX; // select the lub and glb. - for (unsigned i = 0; i < row_ids.size(); ++i) { - unsigned row_id = row_ids[i]; + for (unsigned row_id : row_ids) { if (visited.contains(row_id)) { continue; } @@ -758,8 +927,8 @@ namespace opt { continue; } if (r.m_type == t_eq) { - solve_for(row_id, x); - return; + eq_row = row_id; + continue; } if (r.m_type == t_mod) { mod_rows.push_back(row_id); @@ -792,24 +961,56 @@ namespace opt { } if (!mod_rows.empty()) { - solve_mod(x, mod_rows); - return; + return solve_mod(x, mod_rows, compute_def); } - + + if (eq_row != UINT_MAX) { + return solve_for(eq_row, x, compute_def); + } + + def result; unsigned lub_size = lub_rows.size(); unsigned glb_size = glb_rows.size(); unsigned row_index = (lub_size <= glb_size) ? lub_index : glb_index; - glb_rows.append(lub_rows); // There are only upper or only lower bounds. if (row_index == UINT_MAX) { - for (unsigned i = 0; i < glb_rows.size(); ++i) { - unsigned row_id = glb_rows[i]; - SASSERT(m_rows[row_id].m_alive); - SASSERT(!get_coefficient(row_id, x).is_zero()); - retire_row(row_id); + if (compute_def) { + if (lub_index != UINT_MAX) { + result = solve_for(lub_index, x, true); + } + else if (glb_index != UINT_MAX) { + result = solve_for(glb_index, x, true); + } + else { + result = def(); + m_var2value[x] = rational::zero(); + } + SASSERT(eval(result) == eval(x)); } - return; + else { + for (unsigned row_id : lub_rows) retire_row(row_id); + for (unsigned row_id : glb_rows) retire_row(row_id); + } + return result; + } + + SASSERT(lub_index != UINT_MAX); + SASSERT(glb_index != UINT_MAX); + if (compute_def) { +#if 0 + def d1(m_rows[lub_index], x); + def d2(m_rows[glb_index], x); + result = (d1 + d2)/2; +#else + if (lub_size <= glb_size) { + result = def(m_rows[lub_index], x); + } + else { + result = def(m_rows[glb_index], x); + } + m_var2value[x] = eval(result); +#endif } // The number of matching lower and upper bounds is small. @@ -818,10 +1019,9 @@ namespace opt { (!is_int(x) || lub_is_unit || glb_is_unit)) { for (unsigned i = 0; i < lub_size; ++i) { unsigned row_id1 = lub_rows[i]; - bool last = i + 1 == lub_rows.size(); + bool last = i + 1 == lub_size; rational coeff = get_coefficient(row_id1, x); - for (unsigned j = 0; j < glb_size; ++j) { - unsigned row_id2 = glb_rows[j]; + for (unsigned row_id2 : glb_rows) { if (last) { resolve(row_id1, coeff, row_id2, x); } @@ -831,21 +1031,25 @@ namespace opt { } } } - for (unsigned i = 0; i < lub_size; ++i) { - retire_row(lub_rows[i]); - } - return; + for (unsigned row_id : lub_rows) retire_row(row_id); + + return result; } // General case. rational coeff = get_coefficient(row_index, x); - for (unsigned i = 0; i < glb_rows.size(); ++i) { - unsigned row_id = glb_rows[i]; + for (unsigned row_id : lub_rows) { + if (row_id != row_index) { + resolve(row_index, coeff, row_id, x); + } + } + for (unsigned row_id : glb_rows) { if (row_id != row_index) { resolve(row_index, coeff, row_id, x); } } retire_row(row_index); + return result; } // @@ -863,11 +1067,11 @@ namespace opt { // x := D*x' + u // - void model_based_opt::solve_mod(unsigned x, unsigned_vector const& mod_rows) { + model_based_opt::def model_based_opt::solve_mod(unsigned x, unsigned_vector const& mod_rows, bool compute_def) { SASSERT(!mod_rows.empty()); rational D(1); - for (unsigned i = 0; i < mod_rows.size(); ++i) { - D = lcm(D, m_rows[mod_rows[i]].m_mod); + for (unsigned idx : mod_rows) { + D = lcm(D, m_rows[idx].m_mod); } if (D.is_zero()) { throw default_exception("modulo 0 is not defined"); @@ -876,9 +1080,9 @@ namespace opt { rational val_x = m_var2value[x]; rational u = mod(val_x, D); SASSERT(u.is_nonneg() && u < D); - for (unsigned i = 0; i < mod_rows.size(); ++i) { - replace_var(mod_rows[i], x, u); - SASSERT(invariant(mod_rows[i], m_rows[mod_rows[i]])); + for (unsigned idx : mod_rows) { + replace_var(idx, x, u); + SASSERT(invariant(idx, m_rows[idx])); } // // update inequalities such that u is added to t and @@ -894,15 +1098,20 @@ namespace opt { unsigned y = add_var(new_val, true); unsigned_vector const& row_ids = m_var2row_ids[x]; uint_set visited; - for (unsigned i = 0; i < row_ids.size(); ++i) { - unsigned row_id = row_ids[i]; + for (unsigned row_id : row_ids) { if (!visited.contains(row_id)) { // x |-> D*y + u replace_var(row_id, x, D, y, u); visited.insert(row_id); } } - project(y); + def result = project(y, compute_def); + if (compute_def) { + result = (result * D) + u; + m_var2value[x] = eval(result); + } + SASSERT(!compute_def || eval(result) == eval(x)); + return result; } // update row with: x |-> C @@ -949,39 +1158,67 @@ namespace opt { // 3x + t = 0 & 7 | (c*x + s) & ax <= u // 3 | -t & 21 | (-ct + 3s) & a-t <= 3u - void model_based_opt::solve_for(unsigned row_id1, unsigned x) { + model_based_opt::def model_based_opt::solve_for(unsigned row_id1, unsigned x, bool compute_def) { + TRACE("opt", tout << "v" << x << "\n" << m_rows[row_id1] << "\n";); rational a = get_coefficient(row_id1, x), b; + ineq_type ty = m_rows[row_id1].m_type; SASSERT(!a.is_zero()); - SASSERT(m_rows[row_id1].m_type == t_eq); SASSERT(m_rows[row_id1].m_alive); - if (m_var2is_int[x] && !abs(a).is_one()) { + if (a.is_neg()) { + a.neg(); + m_rows[row_id1].neg(); + } + SASSERT(a.is_pos()); + if (ty == t_lt) { + SASSERT(compute_def); + m_rows[row_id1].m_coeff += a; + } + if (m_var2is_int[x] && !a.is_one()) { row& r1 = m_rows[row_id1]; vector coeffs; mk_coeffs_without(coeffs, r1.m_vars, x); rational c = r1.m_coeff; - add_divides(coeffs, c, abs(a)); + add_divides(coeffs, c, a); } unsigned_vector const& row_ids = m_var2row_ids[x]; uint_set visited; visited.insert(row_id1); - for (unsigned i = 0; i < row_ids.size(); ++i) { - unsigned row_id2 = row_ids[i]; + for (unsigned row_id2 : row_ids) { if (!visited.contains(row_id2)) { - visited.insert(row_id2); + visited.insert(row_id2); b = get_coefficient(row_id2, x); if (!b.is_zero()) { - resolve(row_id1, a, row_id2, x); + row& dst = m_rows[row_id2]; + switch (dst.m_type) { + case t_eq: + case t_lt: + case t_le: + solve(row_id1, a, row_id2, x); + break; + case t_mod: + // mod reduction already done. + UNREACHABLE(); + break; + } } } } + def result; + if (compute_def) { + result = def(m_rows[row_id1], x); + m_var2value[x] = eval(result); + } retire_row(row_id1); + return result; } - - void model_based_opt::project(unsigned num_vars, unsigned const* vars) { + + vector model_based_opt::project(unsigned num_vars, unsigned const* vars, bool compute_def) { + vector result; for (unsigned i = 0; i < num_vars; ++i) { - project(vars[i]); + result.push_back(project(vars[i], compute_def)); TRACE("opt", display(tout << "After projecting: v" << vars[i] << "\n");); } + return result; } } diff --git a/src/math/simplex/model_based_opt.h b/src/math/simplex/model_based_opt.h index 54360d0ac..e52d0cfe0 100644 --- a/src/math/simplex/model_based_opt.h +++ b/src/math/simplex/model_based_opt.h @@ -58,6 +58,25 @@ namespace opt { rational m_value; // value of m_vars + m_coeff under interpretation of m_var2value. bool m_alive; // rows can be marked dead if they have been processed. void reset() { m_vars.reset(); m_coeff.reset(); m_value.reset(); } + + void neg() { for (var & v : m_vars) v.m_coeff.neg(); m_coeff.neg(); m_value.neg(); } + rational get_coefficient(unsigned x) const; + }; + + // A definition is a linear term of the form (vars + coeff) / div + struct def { + def(): m_div(1) {} + def(row const& r, unsigned x); + def(def const& other): m_vars(other.m_vars), m_coeff(other.m_coeff), m_div(other.m_div) {} + vector m_vars; + rational m_coeff; + rational m_div; + def operator+(def const& other) const; + def operator/(unsigned n) const { return *this / rational(n); } + def operator/(rational const& n) const; + def operator*(rational const& n) const; + def operator+(rational const& n) const; + void normalize(); }; private: @@ -81,10 +100,16 @@ namespace opt { rational get_coefficient(unsigned row_id, unsigned var_id) const; - rational get_row_value(row const& r) const; + rational eval(row const& r) const; + + rational eval(unsigned x) const; + + rational eval(def const& d) const; void resolve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x); + void solve(unsigned row_src, rational const& a1, unsigned row_dst, unsigned x); + void mul_add(bool same_sign, unsigned row_id1, rational const& c, unsigned row_id2); void mul_add(unsigned x, rational const& a1, unsigned row_src, rational const& a2, unsigned row_dst); @@ -95,6 +120,8 @@ namespace opt { void sub(unsigned dst, rational const& c); + void del_var(unsigned dst, unsigned x); + void set_row(unsigned row_id, vector const& coeffs, rational const& c, rational const& m, ineq_type rel); void add_constraint(vector const& coeffs, rational const& c, rational const& m, ineq_type r); @@ -117,12 +144,12 @@ namespace opt { void update_value(unsigned x, rational const& val); - void project(unsigned var); + def project(unsigned var, bool compute_def); - void solve_for(unsigned row_id, unsigned x); - - void solve_mod(unsigned x, unsigned_vector const& mod_rows); + def solve_for(unsigned row_id, unsigned x, bool compute_def); + def solve_mod(unsigned x, unsigned_vector const& mod_rows, bool compute_def); + bool is_int(unsigned x) const { return m_var2is_int[x]; } void retire_row(unsigned row_id); @@ -159,7 +186,7 @@ namespace opt { // // Project set of variables from inequalities. // - void project(unsigned num_vars, unsigned const* vars); + vector project(unsigned num_vars, unsigned const* vars, bool compute_def); // // Extract current rows (after projection). @@ -167,13 +194,17 @@ namespace opt { void get_live_rows(vector& rows); void display(std::ostream& out) const; - void display(std::ostream& out, row const& r) const; + static std::ostream& display(std::ostream& out, row const& r); + static std::ostream& display(std::ostream& out, def const& r); + static void display(std::ostream& out, vector const& vars, rational const& coeff); }; } std::ostream& operator<<(std::ostream& out, opt::ineq_type ie); +inline std::ostream& operator<<(std::ostream& out, opt::model_based_opt::def const& d) { return opt::model_based_opt::display(out, d); } +inline std::ostream& operator<<(std::ostream& out, opt::model_based_opt::row const& r) { return opt::model_based_opt::display(out, r); } inline std::ostream& operator<<(std::ostream& out, opt::model_based_opt::var const v) { return out << "v" << v.m_id; } diff --git a/src/model/model.cpp b/src/model/model.cpp index e6c7b74a0..6ddf9765c 100644 --- a/src/model/model.cpp +++ b/src/model/model.cpp @@ -26,16 +26,15 @@ Revision History: #include "model/model_evaluator.h" model::model(ast_manager & m): - model_core(m) { + model_core(m), + m_mev(*this) { } model::~model() { - sort2universe::iterator it3 = m_usort2universe.begin(); - sort2universe::iterator end3 = m_usort2universe.end(); - for (; it3 != end3; ++it3) { - m_manager.dec_ref(it3->m_key); - m_manager.dec_array_ref(it3->m_value->size(), it3->m_value->c_ptr()); - dealloc(it3->m_value); + for (auto & kv : m_usort2universe) { + m_manager.dec_ref(kv.m_key); + m_manager.dec_array_ref(kv.m_value->size(), kv.m_value->c_ptr()); + dealloc(kv.m_value); } } @@ -48,19 +47,13 @@ void model::copy_const_interps(model const & source) { } void model::copy_func_interps(model const & source) { - decl2finterp::iterator it2 = source.m_finterp.begin(); - decl2finterp::iterator end2 = source.m_finterp.end(); - for (; it2 != end2; ++it2) { - register_decl(it2->m_key, it2->m_value->copy()); - } + for (auto const& kv : source.m_finterp) + register_decl(kv.m_key, kv.m_value->copy()); } void model::copy_usort_interps(model const & source) { - sort2universe::iterator it3 = source.m_usort2universe.begin(); - sort2universe::iterator end3 = source.m_usort2universe.end(); - for (; it3 != end3; ++it3) { - register_usort(it3->m_key, it3->m_value->size(), it3->m_value->c_ptr()); - } + for (auto const& kv : source.m_usort2universe) + register_usort(kv.m_key, kv.m_value->size(), kv.m_value->c_ptr()); } model * model::copy() const { @@ -73,12 +66,10 @@ model * model::copy() const { return m; } -// Remark: eval is for backward compatibility. We should use model_evaluator. -bool model::eval(expr * e, expr_ref & result, bool model_completion) { - model_evaluator ev(*this); - ev.set_model_completion(model_completion); +bool model::eval_expr(expr * e, expr_ref & result, bool model_completion) { + scoped_model_completion _smc(*this, model_completion); try { - ev(e, result); + result = (*this)(e); return true; } catch (model_evaluator_exception & ex) { @@ -151,28 +142,21 @@ model * model::translate(ast_translation & translator) const { model * res = alloc(model, translator.to()); // Translate const interps - decl2expr::iterator it1 = m_interp.begin(); - decl2expr::iterator end1 = m_interp.end(); - for (; it1 != end1; ++it1) { - res->register_decl(translator(it1->m_key), translator(it1->m_value)); - } + for (auto const& kv : m_interp) + res->register_decl(translator(kv.m_key), translator(kv.m_value)); // Translate func interps - decl2finterp::iterator it2 = m_finterp.begin(); - decl2finterp::iterator end2 = m_finterp.end(); - for (; it2 != end2; ++it2) { - func_interp * fi = it2->m_value; - res->register_decl(translator(it2->m_key), fi->translate(translator)); + for (auto const& kv : m_finterp) { + func_interp * fi = kv.m_value; + res->register_decl(translator(kv.m_key), fi->translate(translator)); } // Translate usort interps - sort2universe::iterator it3 = m_usort2universe.begin(); - sort2universe::iterator end3 = m_usort2universe.end(); - for (; it3 != end3; ++it3) { + for (auto const& kv : m_usort2universe) { ptr_vector new_universe; - for (unsigned i=0; im_value->size(); i++) - new_universe.push_back(translator(it3->m_value->get(i))); - res->register_usort(translator(it3->m_key), + for (unsigned i=0; i < kv.m_value->size(); i++) + new_universe.push_back(translator(kv.m_value->get(i))); + res->register_usort(translator(kv.m_key), new_universe.size(), new_universe.c_ptr()); } @@ -180,3 +164,30 @@ model * model::translate(ast_translation & translator) const { return res; } +expr_ref model::operator()(expr* t) { + return m_mev(t); +} + +expr_ref_vector model::operator()(expr_ref_vector const& ts) { + expr_ref_vector rs(m()); + for (expr* t : ts) rs.push_back((*this)(t)); + return rs; +} + +bool model::is_true(expr* t) { + return m().is_true((*this)(t)); +} + +bool model::is_false(expr* t) { + return m().is_false((*this)(t)); +} + +bool model::is_true(expr_ref_vector const& ts) { + for (expr* t : ts) if (!is_true(t)) return false; + return true; +} + +void model::reset_eval_cache() { + m_mev.reset(); +} + diff --git a/src/model/model.h b/src/model/model.h index 758d3d451..9399be285 100644 --- a/src/model/model.h +++ b/src/model/model.h @@ -20,15 +20,20 @@ Revision History: #define MODEL_H_ #include "model/model_core.h" +#include "model/model_evaluator.h" #include "util/ref.h" #include "ast/ast_translation.h" +class model; +typedef ref model_ref; + class model : public model_core { protected: typedef obj_map*> sort2universe; - + ptr_vector m_usorts; sort2universe m_usort2universe; + model_evaluator m_mev; struct value_proc; public: @@ -40,10 +45,9 @@ public: void copy_usort_interps(model const & source); model * copy() const; - - bool eval(func_decl * f, expr_ref & r) const { return model_core::eval(f, r); } - bool eval(expr * e, expr_ref & result, bool model_completion = false); - + + bool eval_expr(expr * e, expr_ref & result, bool model_completion = false); + expr * get_some_value(sort * s) override; ptr_vector const & get_universe(sort * s) const override; unsigned get_num_uninterpreted_sorts() const override; @@ -58,9 +62,37 @@ public: // Model translation // model * translate(ast_translation & translator) const; + + void set_model_completion(bool f) { m_mev.set_model_completion(f); } + void updt_params(params_ref const & p) { m_mev.updt_params(p); } + + /** + * evaluation using the model evaluator. Caches results. + */ + expr_ref operator()(expr* t); + expr_ref_vector operator()(expr_ref_vector const& ts); + bool is_true(expr* t); + bool is_false(expr* t); + bool is_true(expr_ref_vector const& ts); + void reset_eval_cache(); + + class scoped_model_completion { + bool m_old_completion; + model& m_model; + public: + scoped_model_completion(model& m, bool c): + m_old_completion(m.m_mev.get_model_completion()), m_model(m) { + m.set_model_completion(c); + } + scoped_model_completion(model_ref& m, bool c): + m_old_completion(m->m_mev.get_model_completion()), m_model(*m.get()) { + m->set_model_completion(c); + } + ~scoped_model_completion() { + m_model.set_model_completion(m_old_completion); + } + }; }; -typedef ref model_ref; #endif /* MODEL_H_ */ - diff --git a/src/model/model_core.cpp b/src/model/model_core.cpp index 648768bf3..d41854c22 100644 --- a/src/model/model_core.cpp +++ b/src/model/model_core.cpp @@ -47,8 +47,8 @@ bool model_core::eval(func_decl* f, expr_ref & r) const { void model_core::register_decl(func_decl * d, expr * v) { SASSERT(d->get_arity() == 0); - decl2expr::obj_map_entry * entry = m_interp.insert_if_not_there2(d, 0); - if (entry->get_data().m_value == 0) { + decl2expr::obj_map_entry * entry = m_interp.insert_if_not_there2(d, nullptr); + if (entry->get_data().m_value == nullptr) { // new entry m_decls.push_back(d); m_const_decls.push_back(d); @@ -67,8 +67,8 @@ void model_core::register_decl(func_decl * d, expr * v) { void model_core::register_decl(func_decl * d, func_interp * fi) { SASSERT(d->get_arity() > 0); SASSERT(&fi->m() == &m_manager); - decl2finterp::obj_map_entry * entry = m_finterp.insert_if_not_there2(d, 0); - if (entry->get_data().m_value == 0) { + decl2finterp::obj_map_entry * entry = m_finterp.insert_if_not_there2(d, nullptr); + if (entry->get_data().m_value == nullptr) { // new entry m_decls.push_back(d); m_func_decls.push_back(d); diff --git a/src/model/model_core.h b/src/model/model_core.h index ce211c0b8..3f1e92bad 100644 --- a/src/model/model_core.h +++ b/src/model/model_core.h @@ -40,6 +40,7 @@ public: virtual ~model_core(); ast_manager & get_manager() const { return m_manager; } + ast_manager& m() const { return m_manager; } unsigned get_num_decls() const { return m_decls.size(); } func_decl * get_decl(unsigned i) const { return m_decls[i]; } diff --git a/src/model/model_evaluator.cpp b/src/model/model_evaluator.cpp index 0b25a250b..f7c7fa74d 100644 --- a/src/model/model_evaluator.cpp +++ b/src/model/model_evaluator.cpp @@ -53,6 +53,7 @@ struct evaluator_cfg : public default_rewriter_cfg { bool m_model_completion; bool m_cache; bool m_array_equalities; + bool m_array_as_stores; evaluator_cfg(ast_manager & m, model_core & md, params_ref const & p): m(m), @@ -84,9 +85,10 @@ struct evaluator_cfg : public default_rewriter_cfg { model_evaluator_params p(_p); m_max_memory = megabytes_to_bytes(p.max_memory()); m_max_steps = p.max_steps(); - m_model_completion = p.completion(); m_cache = p.cache(); + m_model_completion = p.completion(); m_array_equalities = p.array_equalities(); + m_array_as_stores = p.array_as_stores(); } bool evaluate(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { @@ -201,15 +203,16 @@ struct evaluator_cfg : public default_rewriter_cfg { return st; } - void expand_value(expr_ref& val) { + void expand_stores(expr_ref& val) { vector stores; expr_ref else_case(m); bool _unused; - if (m_ar.is_array(val) && extract_array_func_interp(val, stores, else_case, _unused)) { + if (m_array_as_stores && + m_ar.is_array(val) && + extract_array_func_interp(val, stores, else_case, _unused)) { sort* srt = m.get_sort(val); val = m_ar.mk_const_array(srt, else_case); - for (unsigned i = stores.size(); i > 0; ) { - --i; + for (unsigned i = stores.size(); i-- > 0; ) { expr_ref_vector args(m); args.push_back(val); args.append(stores[i].size(), stores[i].c_ptr()); @@ -288,8 +291,8 @@ struct evaluator_cfg : public default_rewriter_cfg { bool cache_results() const { return m_cache; } - br_status mk_array_eq(expr* a, expr* b, expr_ref& result) { + TRACE("model_evaluator", tout << "mk_array_eq " << m_array_equalities << "\n";); if (a == b) { result = m.mk_true(); return BR_DONE; @@ -315,6 +318,7 @@ struct evaluator_cfg : public default_rewriter_cfg { conj.push_back(m.mk_eq(else1, else2)); } if (args_are_unique1 && args_are_unique2 && !stores1.empty()) { + TRACE("model_evalator", tout << "argss are unique";); return mk_array_eq_core(stores1, else1, stores2, else2, conj, result); } @@ -329,7 +333,10 @@ struct evaluator_cfg : public default_rewriter_cfg { expr_ref s2(m_ar.mk_select(args2.size(), args2.c_ptr()), m); conj.push_back(m.mk_eq(s1, s2)); } - result = m.mk_and(conj.size(), conj.c_ptr()); + result = mk_and(conj); + TRACE("model_evaluator", tout << mk_pp(a, m) << " == " << mk_pp(b, m) << " -> " << conj << "\n"; + for (auto& s : stores1) tout << "store: " << s << "\n"; + ); return BR_REWRITE_FULL; } return BR_FAILED; @@ -454,7 +461,7 @@ struct evaluator_cfg : public default_rewriter_cfg { func_decl* f = m_ar.get_as_array_func_decl(to_app(a)); func_interp* g = m_model.get_func_interp(f); - if (!g) return false; + if (!g) return false; unsigned sz = g->num_entries(); unsigned arity = f->get_arity(); unsigned base_sz = stores.size(); @@ -497,9 +504,6 @@ struct evaluator_cfg : public default_rewriter_cfg { TRACE("model_evaluator", tout << "else case: " << mk_pp(else_case, m) << "\n";); return true; } - - - }; template class rewriter_tpl; @@ -513,10 +517,7 @@ struct model_evaluator::imp : public rewriter_tpl { m_cfg(md.get_manager(), md, p) { set_cancel_check(false); } - - void expand_value (expr_ref &val) { - m_cfg.expand_value (val); - } + void expand_stores(expr_ref &val) {m_cfg.expand_stores(val);} }; model_evaluator::model_evaluator(model_core & md, params_ref const & p) { @@ -543,6 +544,10 @@ void model_evaluator::set_model_completion(bool f) { m_imp->cfg().m_model_completion = f; } +bool model_evaluator::get_model_completion() const { + return m_imp->cfg().m_model_completion; +} + void model_evaluator::set_expand_array_equalities(bool f) { m_imp->cfg().m_array_equalities = f; } @@ -562,10 +567,16 @@ void model_evaluator::reset(params_ref const & p) { updt_params(p); } +void model_evaluator::reset(model_core &model, params_ref const& p) { + dealloc(m_imp); + m_imp = alloc(imp, model, p); +} + + void model_evaluator::operator()(expr * t, expr_ref & result) { TRACE("model_evaluator", tout << mk_ismt2_pp(t, m()) << "\n";); m_imp->operator()(t, result); - m_imp->expand_value(result); + m_imp->expand_stores(result); } expr_ref model_evaluator::operator()(expr * t) { @@ -574,3 +585,44 @@ expr_ref model_evaluator::operator()(expr * t) { this->operator()(t, result); return result; } + +expr_ref_vector model_evaluator::operator()(expr_ref_vector const& ts) { + expr_ref_vector rs(m()); + for (expr* t : ts) rs.push_back((*this)(t)); + return rs; +} + + +bool model_evaluator::is_true(expr* t) { + expr_ref tmp(m()); + return eval(t, tmp, true) && m().is_true(tmp); +} + +bool model_evaluator::is_false(expr* t) { + expr_ref tmp(m()); + return eval(t, tmp, true) && m().is_false(tmp); +} + +bool model_evaluator::is_true(expr_ref_vector const& ts) { + for (expr* t : ts) if (!is_true(t)) return false; + return true; +} + +bool model_evaluator::eval(expr* t, expr_ref& r, bool model_completion) { + set_model_completion(model_completion); + try { + r = (*this)(t); + return true; + } + catch (model_evaluator_exception &ex) { + (void)ex; + TRACE("model_evaluator", tout << ex.msg () << "\n";); + return false; + } +} + +bool model_evaluator::eval(expr_ref_vector const& ts, expr_ref& r, bool model_completion) { + expr_ref tmp(m()); + tmp = mk_and(ts); + return eval(tmp, r, model_completion); +} diff --git a/src/model/model_evaluator.h b/src/model/model_evaluator.h index bd2b2d664..8666e3519 100644 --- a/src/model/model_evaluator.h +++ b/src/model/model_evaluator.h @@ -37,17 +37,32 @@ public: ast_manager & m () const; void set_model_completion(bool f); + bool get_model_completion() const; void set_expand_array_equalities(bool f); void updt_params(params_ref const & p); static void get_param_descrs(param_descrs & r); void operator()(expr * t, expr_ref & r); - expr_ref operator()(expr* t); + expr_ref_vector operator()(expr_ref_vector const& ts); + + // exception safe + bool eval(expr* t, expr_ref& r, bool model_completion = true); + bool eval(expr_ref_vector const& ts, expr_ref& r, bool model_completion = true); + + bool is_true(expr * t); + bool is_false(expr * t); + bool is_true(expr_ref_vector const& ts); + + /** + * best effort evaluator of extensional array equality. + */ + expr_ref eval_array_eq(app* e, expr* arg1, expr* arg2); void cleanup(params_ref const & p = params_ref()); void reset(params_ref const & p = params_ref()); + void reset(model_core& model, params_ref const & p = params_ref()); unsigned get_num_steps() const; }; diff --git a/src/model/model_evaluator_params.pyg b/src/model/model_evaluator_params.pyg index b6417f7fc..509b3e7c7 100644 --- a/src/model/model_evaluator_params.pyg +++ b/src/model/model_evaluator_params.pyg @@ -4,6 +4,7 @@ def_module_params('model_evaluator', max_steps_param(), ('completion', BOOL, False, 'assigns an interptetation to symbols that do not have one in the current model, when evaluating expressions in the current model'), ('cache', BOOL, True, 'cache intermediate results in the model evaluator'), - ('array_equalities', BOOL, True, 'evaluate array equalities') + ('array_equalities', BOOL, True, 'evaluate array equalities'), + ('array_as_stores', BOOL, True, 'return array as a set of stores'), )) diff --git a/src/model/model_implicant.cpp b/src/model/model_implicant.cpp index 3456c2746..375834477 100644 --- a/src/model/model_implicant.cpp +++ b/src/model/model_implicant.cpp @@ -172,7 +172,6 @@ void model_implicant::process_formula(app* e, ptr_vector& todo, ptr_vector case OP_FALSE: break; case OP_EQ: - case OP_IFF: if (args[0] == args[1]) { SASSERT(v); // no-op @@ -536,9 +535,8 @@ bool model_implicant::extract_array_func_interp(expr* a, vector */ void model_implicant::eval_array_eq(app* e, expr* arg1, expr* arg2) { TRACE("pdr", tout << "array equality: " << mk_pp(e, m) << "\n";); - expr_ref v1(m), v2(m); - m_model->eval(arg1, v1); - m_model->eval(arg2, v2); + expr_ref v1 = (*m_model)(arg1); + expr_ref v2 = (*m_model)(arg2); if (v1 == v2) { set_true(e); return; @@ -588,8 +586,8 @@ void model_implicant::eval_array_eq(app* e, expr* arg1, expr* arg2) { args2.append(store[i].size()-1, store[i].c_ptr()); s1 = m_array.mk_select(args1.size(), args1.c_ptr()); s2 = m_array.mk_select(args2.size(), args2.c_ptr()); - m_model->eval(s1, w1); - m_model->eval(s2, w2); + w1 = (*m_model)(s1); + w2 = (*m_model)(s2); if (w1 == w2) { continue; } @@ -622,9 +620,9 @@ void model_implicant::eval_eq(app* e, expr* arg1, expr* arg2) { eval_array_eq(e, arg1, arg2); } else if (is_x(arg1) || is_x(arg2)) { - expr_ref eq(m), vl(m); + expr_ref eq(m); eq = m.mk_eq(arg1, arg2); - m_model->eval(eq, vl); + expr_ref vl = (*m_model)(eq); if (m.is_true(vl)) { set_bool(e, true); } @@ -742,10 +740,6 @@ void model_implicant::eval_basic(app* e) { set_x(e); } break; - case OP_IFF: - VERIFY(m.is_iff(e, arg1, arg2)); - eval_eq(e, arg1, arg2); - break; case OP_ITE: VERIFY(m.is_ite(e, argCond, argThen, argElse)); if (is_true(argCond)) { @@ -842,8 +836,7 @@ bool model_implicant::check_model(ptr_vector const& formulas) { eval_basic(curr); } else { - expr_ref vl(m); - m_model->eval(curr, vl); + expr_ref vl = (*m_model)(curr); assign_value(curr, vl); } @@ -889,7 +882,7 @@ expr_ref model_implicant::eval(model_ref& model, func_decl* d) { expr_ref model_implicant::eval(model_ref& model, expr* e) { expr_ref result(m); m_model = model; - VERIFY(m_model->eval(e, result, true)); + result = (*m_model)(e); if (m_array.is_array(e)) { vector stores; expr_ref_vector args(m); diff --git a/src/model/model_smt2_pp.cpp b/src/model/model_smt2_pp.cpp index 152cbc8d3..bc1900ba7 100644 --- a/src/model/model_smt2_pp.cpp +++ b/src/model/model_smt2_pp.cpp @@ -302,3 +302,8 @@ void model_smt2_pp(std::ostream & out, ast_manager & m, model_core const & md, u pp_consts(out, *(ctx.get()), md, indent); pp_funs(out, *(ctx.get()), md, indent); } + +std::ostream& operator<<(std::ostream& out, model_core const& m) { + model_smt2_pp(out, m.m(), m, 0); + return out; +} diff --git a/src/model/model_smt2_pp.h b/src/model/model_smt2_pp.h index c43e26ca6..b38bd631d 100644 --- a/src/model/model_smt2_pp.h +++ b/src/model/model_smt2_pp.h @@ -25,5 +25,6 @@ Revision History: void model_smt2_pp(std::ostream & out, ast_printer_context & ctx, model_core const & m, unsigned indent); void model_smt2_pp(std::ostream & out, ast_manager & m, model_core const & md, unsigned indent); +std::ostream& operator<<(std::ostream& out, model_core const& m); #endif diff --git a/src/muz/base/CMakeLists.txt b/src/muz/base/CMakeLists.txt index 6b0334664..8c21e1557 100644 --- a/src/muz/base/CMakeLists.txt +++ b/src/muz/base/CMakeLists.txt @@ -18,5 +18,5 @@ z3_add_component(muz smt smt2parser PYG_FILES - fixedpoint_params.pyg + fp_params.pyg ) diff --git a/src/muz/base/dl_context.cpp b/src/muz/base/dl_context.cpp index cc14de5ce..220b2516d 100644 --- a/src/muz/base/dl_context.cpp +++ b/src/muz/base/dl_context.cpp @@ -27,7 +27,7 @@ Revision History: #include "ast/ast_smt2_pp.h" #include "ast/datatype_decl_plugin.h" #include "ast/scoped_proof.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include "ast/ast_pp_util.h" @@ -152,15 +152,15 @@ namespace datalog { class context::restore_rules : public trail { rule_set* m_old_rules; - void reset() { - dealloc(m_old_rules); + void reset() { + dealloc(m_old_rules); m_old_rules = nullptr; } public: restore_rules(rule_set& r): m_old_rules(alloc(rule_set, r)) {} ~restore_rules() override {} - + void undo(context& ctx) override { ctx.replace_rules(*m_old_rules); reset(); @@ -188,10 +188,8 @@ namespace datalog { if (m_trail.get_num_scopes() == 0) { throw default_exception("there are no backtracking points to pop to"); } - if (m_engine.get()) { - throw default_exception("pop operation is only supported by duality engine"); - } - m_trail.pop_scope(1); + throw default_exception("pop operation is not supported"); + m_trail.pop_scope(1); } // ----------------------------------- @@ -205,7 +203,7 @@ namespace datalog { m_register_engine(re), m_fparams(fp), m_params_ref(pa), - m_params(alloc(fixedpoint_params, m_params_ref)), + m_params(alloc(fp_params, m_params_ref)), m_decl_util(m), m_rewriter(m), m_var_subst(m), @@ -237,7 +235,7 @@ namespace datalog { context::~context() { reset(); - dealloc(m_params); + dealloc(m_params); } void context::reset() { @@ -293,14 +291,14 @@ namespace datalog { bool context::similarity_compressor() const { return m_params->datalog_similarity_compressor(); } unsigned context::similarity_compressor_threshold() const { return m_params->datalog_similarity_compressor_threshold(); } unsigned context::soft_timeout() const { return m_fparams.m_timeout; } - unsigned context::initial_restart_timeout() const { return m_params->datalog_initial_restart_timeout(); } + unsigned context::initial_restart_timeout() const { return m_params->datalog_initial_restart_timeout(); } bool context::generate_explanations() const { return m_params->datalog_generate_explanations(); } bool context::explanations_on_relation_level() const { return m_params->datalog_explanations_on_relation_level(); } bool context::magic_sets_for_queries() const { return m_params->datalog_magic_sets_for_queries(); } symbol context::tab_selection() const { return m_params->tab_selection(); } bool context::xform_coi() const { return m_params->xform_coi(); } bool context::xform_slice() const { return m_params->xform_slice(); } - bool context::xform_bit_blast() const { return m_params->xform_bit_blast(); } + bool context::xform_bit_blast() const { return m_params->xform_bit_blast(); } bool context::karr() const { return m_params->xform_karr(); } bool context::scale() const { return m_params->xform_scale(); } bool context::magic() const { return m_params->xform_magic(); } @@ -430,7 +428,7 @@ namespace datalog { } - void context::set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, + void context::set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, symbol const * relation_names) { if (relation_name_cnt > 0) { ensure_engine(); @@ -440,9 +438,9 @@ namespace datalog { } } - func_decl * context::mk_fresh_head_predicate(symbol const & prefix, symbol const & suffix, + func_decl * context::mk_fresh_head_predicate(symbol const & prefix, symbol const & suffix, unsigned arity, sort * const * domain, func_decl* orig_pred) { - func_decl* new_pred = + func_decl* new_pred = m.mk_fresh_func_decl(prefix, suffix, arity, domain, m.mk_bool_sort()); register_predicate(new_pred, true); @@ -475,7 +473,7 @@ namespace datalog { // // Update a rule with a new. // It requires basic subsumption. - // + // void context::update_rule(expr* rl, symbol const& name) { datalog::rule_manager& rm = get_rule_manager(); proof* p = nullptr; @@ -496,13 +494,13 @@ namespace datalog { rule* old_rule = nullptr; for (unsigned i = 0; i < size_before; ++i) { if (rls[i]->name() == name) { - if (old_rule) { + if (old_rule) { std::stringstream strm; strm << "Rule " << name << " occurs twice. It cannot be modified"; m_rule_set.del_rule(r); throw default_exception(strm.str()); } - old_rule = rls[i]; + old_rule = rls[i]; } } if (old_rule) { @@ -558,7 +556,7 @@ namespace datalog { ensure_engine(); m_engine->add_cover(level, pred, property); } - + void context::add_invariant(func_decl* pred, expr *property) { ensure_engine(); @@ -568,34 +566,28 @@ namespace datalog { void context::check_rules(rule_set& r) { m_rule_properties.set_generate_proof(generate_proof_trace()); switch(get_engine()) { - case DATALOG_ENGINE: + case DATALOG_ENGINE: m_rule_properties.collect(r); m_rule_properties.check_quantifier_free(); m_rule_properties.check_uninterpreted_free(); - m_rule_properties.check_nested_free(); + m_rule_properties.check_nested_free(); m_rule_properties.check_infinite_sorts(); break; case SPACER_ENGINE: - case PDR_ENGINE: m_rule_properties.collect(r); m_rule_properties.check_existential_tail(); m_rule_properties.check_for_negated_predicates(); m_rule_properties.check_uninterpreted_free(); break; - case QPDR_ENGINE: - m_rule_properties.collect(r); - m_rule_properties.check_for_negated_predicates(); - m_rule_properties.check_uninterpreted_free(); - break; case BMC_ENGINE: m_rule_properties.collect(r); m_rule_properties.check_for_negated_predicates(); - break; + break; case QBMC_ENGINE: m_rule_properties.collect(r); m_rule_properties.check_existential_tail(); m_rule_properties.check_for_negated_predicates(); - break; + break; case TAB_ENGINE: m_rule_properties.collect(r); m_rule_properties.check_existential_tail(); @@ -658,7 +650,7 @@ namespace datalog { add_fact(pred, rfact); } } - + void context::add_table_fact(func_decl * pred, unsigned num_args, unsigned args[]) { if (pred->get_arity() != num_args) { std::ostringstream out; @@ -690,7 +682,7 @@ namespace datalog { reopen(); } } - + void context::reopen() { SASSERT(m_closed); m_rule_set.reopen(); @@ -703,7 +695,7 @@ namespace datalog { transformer.register_plugin(plugin); transform_rules(transformer); } - + void context::transform_rules(rule_transformer& transf) { SASSERT(m_closed); //we must finish adding rules before we start transforming them TRACE("dl", display_rules(tout);); @@ -732,7 +724,7 @@ namespace datalog { } void context::collect_params(param_descrs& p) { - fixedpoint_params::collect_param_descrs(p); + fp_params::collect_param_descrs(p); insert_timeout(p); } @@ -740,8 +732,8 @@ namespace datalog { m_params_ref.copy(p); if (m_engine.get()) m_engine->updt_params(); m_generate_proof_trace = m_params->generate_proof_trace(); - m_unbound_compressor = m_params->datalog_unbound_compressor(); - m_default_relation = m_params->datalog_default_relation(); + m_unbound_compressor = m_params->datalog_unbound_compressor(); + m_default_relation = m_params->datalog_default_relation(); } expr_ref context::get_background_assertion() { @@ -756,7 +748,7 @@ namespace datalog { void context::assert_expr(expr* e) { TRACE("dl", tout << mk_ismt2_pp(e, m) << "\n";); - m_background.push_back(e); + m_background.push_back(e); } void context::cleanup() { @@ -776,19 +768,14 @@ namespace datalog { DL_ENGINE get_engine() const { return m_engine_type; } void operator()(expr* e) { - if (is_quantifier(e)) { - m_engine_type = QPDR_ENGINE; - } - else if (m_engine_type != QPDR_ENGINE) { if (a.is_int_real(e)) { - m_engine_type = PDR_ENGINE; + m_engine_type = SPACER_ENGINE; } else if (is_var(e) && m.is_bool(e)) { - m_engine_type = PDR_ENGINE; + m_engine_type = SPACER_ENGINE; } else if (dt.is_datatype(m.get_sort(e))) { - m_engine_type = PDR_ENGINE; - } + m_engine_type = SPACER_ENGINE; } } }; @@ -798,19 +785,13 @@ namespace datalog { return; } symbol e = m_params->engine(); - + if (e == symbol("datalog")) { m_engine_type = DATALOG_ENGINE; } else if (e == symbol("spacer")) { m_engine_type = SPACER_ENGINE; } - else if (e == symbol("pdr")) { - m_engine_type = PDR_ENGINE; - } - else if (e == symbol("qpdr")) { - m_engine_type = QPDR_ENGINE; - } else if (e == symbol("bmc")) { m_engine_type = BMC_ENGINE; } @@ -830,7 +811,7 @@ namespace datalog { if (m_engine_type == LAST_ENGINE) { expr_fast_mark1 mark; engine_type_proc proc(m); - m_engine_type = DATALOG_ENGINE; + m_engine_type = DATALOG_ENGINE; for (unsigned i = 0; m_engine_type == DATALOG_ENGINE && i < m_rule_set.get_num_rules(); ++i) { rule * r = m_rule_set.get_rule(i); quick_for_each_expr(proc, mark, r->get_head()); @@ -858,8 +839,6 @@ namespace datalog { switch (get_engine()) { case DATALOG_ENGINE: case SPACER_ENGINE: - case PDR_ENGINE: - case QPDR_ENGINE: case BMC_ENGINE: case QBMC_ENGINE: case TAB_ENGINE: @@ -882,8 +861,6 @@ namespace datalog { switch (get_engine()) { case DATALOG_ENGINE: case SPACER_ENGINE: - case PDR_ENGINE: - case QPDR_ENGINE: case BMC_ENGINE: case QBMC_ENGINE: case TAB_ENGINE: @@ -916,15 +893,15 @@ namespace datalog { m_rel = dynamic_cast(m_engine.get()); } - } + } } - lbool context::rel_query(unsigned num_rels, func_decl * const* rels) { + lbool context::rel_query(unsigned num_rels, func_decl * const* rels) { m_last_answer = nullptr; ensure_engine(); return m_engine->query(num_rels, rels); } - + expr* context::get_answer_as_formula() { if (m_last_answer) { return m_last_answer.get(); @@ -977,7 +954,7 @@ namespace datalog { void context::display(std::ostream & out) const { display_rules(out); - if (m_rel) m_rel->display_facts(out); + if (m_rel) m_rel->display_facts(out); } void context::display_profile(std::ostream& out) const { @@ -1013,10 +990,10 @@ namespace datalog { bool context::result_contains_fact(relation_fact const& f) { return m_rel && m_rel->result_contains_fact(f); } - + // NB: algebraic data-types declarations will not be printed. - static void collect_free_funcs(unsigned sz, expr* const* exprs, + static void collect_free_funcs(unsigned sz, expr* const* exprs, ast_pp_util& v, mk_fresh_name& fresh_names) { v.collect(sz, exprs); @@ -1028,7 +1005,7 @@ namespace datalog { fresh_names.add(e); } } - + void context::get_raw_rule_formulas(expr_ref_vector& rules, svector& names, unsigned_vector &bounds) { for (unsigned i = 0; i < m_rule_fmls.size(); ++i) { expr_ref r = bind_vars(m_rule_fmls[i].get(), true); @@ -1041,7 +1018,7 @@ namespace datalog { void context::get_rules_as_formulas(expr_ref_vector& rules, expr_ref_vector& queries, svector& names) { expr_ref fml(m); rule_manager& rm = get_rule_manager(); - + // ensure that rules are all using bound variables. for (unsigned i = m_rule_fmls_head; i < m_rule_fmls.size(); ++i) { m_free_vars(m_rule_fmls[i].get()); @@ -1090,7 +1067,7 @@ namespace datalog { } for (unsigned i = m_rule_fmls_head; i < m_rule_fmls.size(); ++i) { rules.push_back(m_rule_fmls[i].get()); - names.push_back(m_rule_names[i]); + names.push_back(m_rule_names[i]); } } @@ -1103,7 +1080,7 @@ namespace datalog { } return out; } - + void context::display_smt2(unsigned num_queries, expr* const* qs, std::ostream& out) { ast_manager& m = get_manager(); ast_pp_util visitor(m); @@ -1132,7 +1109,7 @@ namespace datalog { for (unsigned i = 0; i < sz; ++i) { func_decl* f = visitor.coll.get_func_decls()[i]; if (f->get_family_id() != null_family_id) { - // + // } else if (is_predicate(f) && use_fixedpoint_extensions) { rels.insert(f); @@ -1145,12 +1122,12 @@ namespace datalog { if (!use_fixedpoint_extensions) { out << "(set-logic HORN)\n"; } - for (func_decl * f : rels) + for (func_decl * f : rels) visitor.remove_decl(f); visitor.display_decls(out); - for (func_decl * f : rels) + for (func_decl * f : rels) display_rel_decl(out, f); if (use_fixedpoint_extensions && do_declare_vars) { @@ -1166,7 +1143,7 @@ namespace datalog { PP(axioms[i]); out << ")\n"; } - for (unsigned i = 0; i < rules.size(); ++i) { + for (unsigned i = 0; i < rules.size(); ++i) { out << (use_fixedpoint_extensions?"(rule ":"(assert "); expr* r = rules[i].get(); symbol nm = names[i]; @@ -1179,7 +1156,7 @@ namespace datalog { while (fresh_names.contains(nm)) { std::ostringstream s; s << nm << "!"; - nm = symbol(s.str().c_str()); + nm = symbol(s.str().c_str()); } fresh_names.add(nm); display_symbol(out, nm) << ")"; @@ -1205,7 +1182,7 @@ namespace datalog { args.push_back(m.mk_var(j, m_free_vars[j])); } qfn = m.mk_implies(q, m.mk_app(fn, args.size(), args.c_ptr())); - + out << "(assert "; PP(qfn); out << ")\n"; @@ -1232,7 +1209,7 @@ namespace datalog { smt2_pp_environment_dbg env(m); out << "(declare-rel "; display_symbol(out, f->get_name()) << " ("; - for (unsigned i = 0; i < f->get_arity(); ++i) { + for (unsigned i = 0; i < f->get_arity(); ++i) { ast_smt2_pp(out, f->get_domain(i), env); if (i + 1 < f->get_arity()) { out << " "; @@ -1262,12 +1239,12 @@ namespace datalog { void context::declare_vars(expr_ref_vector& rules, mk_fresh_name& fresh_names, std::ostream& out) { // // replace bound variables in rules by 'var declarations' - // First remove quantifers, then replace bound variables + // First remove quantifers, then replace bound variables // by fresh constants. - // + // smt2_pp_environment_dbg env(m); var_subst vsubst(m, false); - + expr_ref_vector fresh_vars(m), subst(m); expr_ref res(m); obj_map var_idxs; @@ -1280,7 +1257,7 @@ namespace datalog { quantifier* q = to_quantifier(r); if (!q->is_forall()) { continue; - } + } if (has_quantifiers(q->get_expr())) { continue; } @@ -1310,7 +1287,7 @@ namespace datalog { fresh_vars.push_back(m.mk_const(name, s)); out << "(declare-var " << name << " "; ast_smt2_pp(out, s, env); - out << ")\n"; + out << ")\n"; } subst.push_back(fresh_vars[vars[max_var]].get()); } @@ -1322,4 +1299,3 @@ namespace datalog { }; - diff --git a/src/muz/base/dl_context.h b/src/muz/base/dl_context.h index 865c746db..6e6bc80dc 100644 --- a/src/muz/base/dl_context.h +++ b/src/muz/base/dl_context.h @@ -44,7 +44,7 @@ Revision History: #include "muz/base/bind_variables.h" #include "muz/base/rule_properties.h" -struct fixedpoint_params; +struct fp_params; namespace datalog { @@ -98,7 +98,7 @@ namespace datalog { relation_fact(ast_manager & m) : app_ref_vector(m) {} relation_fact(ast_manager & m, unsigned sz) : app_ref_vector(m) { resize(sz); } relation_fact(context & ctx); - + iterator begin() const { return c_ptr(); } iterator end() const { return c_ptr()+size(); } @@ -126,7 +126,7 @@ namespace datalog { virtual bool has_facts(func_decl * pred) const = 0; virtual void store_relation(func_decl * pred, relation_base * rel) = 0; virtual void inherit_predicate_kind(func_decl* new_pred, func_decl* orig_pred) = 0; - virtual void set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, + virtual void set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, symbol const * relation_names) = 0; virtual bool output_profile() const = 0; virtual void collect_non_empty_predicates(func_decl_set& preds) = 0; @@ -147,7 +147,7 @@ namespace datalog { public: contains_pred(context& ctx): ctx(ctx) {} ~contains_pred() override {} - + bool operator()(expr* e) override { return ctx.is_predicate(e); } @@ -170,7 +170,7 @@ namespace datalog { register_engine_base& m_register_engine; smt_params & m_fparams; params_ref m_params_ref; - fixedpoint_params* m_params; + fp_params* m_params; bool m_generate_proof_trace; // cached configuration parameter bool m_unbound_compressor; // cached configuration parameter symbol m_default_relation; // cached configuration parameter @@ -227,7 +227,7 @@ namespace datalog { void push(); void pop(); - + bool saturation_was_run() const { return m_saturation_was_run; } void notify_saturation_was_run() { m_saturation_was_run = true; } @@ -236,7 +236,7 @@ namespace datalog { ast_manager & get_manager() const { return m; } rule_manager & get_rule_manager() { return m_rule_manager; } smt_params & get_fparams() const { return m_fparams; } - fixedpoint_params const& get_params() const { return *m_params; } + fp_params const& get_params() const { return *m_params; } DL_ENGINE get_engine() { configure_engine(); return m_engine_type; } register_engine_base& get_register_engine() { return m_register_engine; } th_rewriter& get_rewriter() { return m_rewriter; } @@ -251,7 +251,7 @@ namespace datalog { symbol default_table() const; symbol default_relation() const; void set_default_relation(symbol const& s); - symbol default_table_checker() const; + symbol default_table_checker() const; symbol check_relation() const; bool default_table_checked() const; bool dbg_fpr_nonempty_relation_signature() const; @@ -275,7 +275,7 @@ namespace datalog { bool compress_unbound() const; bool quantify_arrays() const; bool instantiate_quantifiers() const; - bool xform_bit_blast() const; + bool xform_bit_blast() const; bool xform_slice() const; bool xform_coi() const; bool array_blast() const; @@ -291,9 +291,9 @@ namespace datalog { void register_variable(func_decl* var); /* - Replace constants that have been registered as + Replace constants that have been registered as variables by de-Bruijn indices and corresponding - universal (if is_forall is true) or existential + universal (if is_forall is true) or existential quantifier. */ expr_ref bind_vars(expr* fml, bool is_forall); @@ -303,7 +303,7 @@ namespace datalog { /** Register datalog relation. - If named is true, we associate the predicate with its name, so that it can be + If named is true, we associate the predicate with its name, so that it can be retrieved by the try_get_predicate_decl() function. Auxiliary predicates introduced e.g. by rule transformations do not need to be named. */ @@ -326,7 +326,7 @@ namespace datalog { /** \brief If a predicate name has a \c func_decl object assigned, return pointer to it; otherwise return 0. - + Not all \c func_decl object used as relation identifiers need to be assigned to their names. Generally, the names coming from the parses are registered here. */ @@ -334,13 +334,13 @@ namespace datalog { func_decl * res = nullptr; m_preds_by_name.find(pred_name, res); return res; - } + } /** \brief Create a fresh head predicate declaration. */ - func_decl * mk_fresh_head_predicate(symbol const & prefix, symbol const & suffix, + func_decl * mk_fresh_head_predicate(symbol const & prefix, symbol const & suffix, unsigned arity, sort * const * domain, func_decl* orig_pred=nullptr); @@ -365,13 +365,13 @@ namespace datalog { /** \brief Assign names of variables used in the declaration of a predicate. - These names are used when printing out the relations to make the output conform + These names are used when printing out the relations to make the output conform to the one of bddbddb. */ void set_argument_names(const func_decl * pred, const svector & var_names); symbol get_argument_name(const func_decl * pred, unsigned arg_index); - void set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, + void set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, symbol const * relation_names); void set_output_predicate(func_decl * pred) { m_rule_set.set_output_predicate(pred); } @@ -385,9 +385,9 @@ namespace datalog { void add_fact(func_decl * pred, const relation_fact & fact); bool has_facts(func_decl * pred) const; - + void add_rule(rule_ref& r); - + void assert_expr(expr* e); expr_ref get_background_assertion(); unsigned get_num_assertions() { return m_background.size(); } @@ -397,7 +397,7 @@ namespace datalog { Method exposed from API for adding rules. */ void add_rule(expr* rl, symbol const& name, unsigned bound = UINT_MAX); - + /** Update a named rule. @@ -421,9 +421,9 @@ namespace datalog { at 'level+1', 'level+2' etc, and include level=-1. */ expr_ref get_cover_delta(int level, func_decl* pred); - + /** - Add a property of predicate 'pred' at 'level'. + Add a property of predicate 'pred' at 'level'. It gets pushed forward when possible. */ void add_cover(int level, func_decl* pred, expr* property); @@ -432,7 +432,7 @@ namespace datalog { Add an invariant of predicate 'pred'. */ void add_invariant (func_decl *pred, expr *property); - + /** \brief Check rule subsumption. */ @@ -471,15 +471,15 @@ namespace datalog { proof_converter_ref& get_proof_converter() { return m_pc; } void add_proof_converter(proof_converter* pc) { m_pc = concat(m_pc.get(), pc); } - void transform_rules(rule_transformer& transf); + void transform_rules(rule_transformer& transf); void transform_rules(rule_transformer::plugin* plugin); void replace_rules(rule_set const& rs); void record_transformed_rules(); - void apply_default_transformation(); + void apply_default_transformation(); void collect_params(param_descrs& r); - + void updt_params(params_ref const& p); void display_rules(std::ostream & out) const { @@ -507,7 +507,7 @@ namespace datalog { /** \brief check if query 'q' is satisfied under asserted rules and background. - If successful, return OK and into \c result assign a relation with all + If successful, return OK and into \c result assign a relation with all tuples matching the query. Otherwise return reason for failure and do not modify \c result. @@ -515,7 +515,7 @@ namespace datalog { starting from zero. The caller becomes an owner of the relation object returned in \c result. The - relation object, however, should not outlive the datalog context since it is + relation object, however, should not outlive the datalog context since it is linked to a relation plugin in the context. */ @@ -524,7 +524,7 @@ namespace datalog { lbool query_from_lvl (expr* q, unsigned lvl); /** \brief retrieve model from inductive invariant that shows query is unsat. - + \pre engine == 'pdr' || engine == 'duality' - this option is only supported for PDR mode and Duality mode. */ @@ -532,7 +532,7 @@ namespace datalog { /** \brief retrieve proof from derivation of the query. - + \pre engine == 'pdr' || engine == 'duality'- this option is only supported for PDR mode and Duality mode. */ @@ -588,12 +588,25 @@ namespace datalog { rel_context_base* get_rel_context() { ensure_engine(); return m_rel; } + void add_callback(void *state, + const datalog::t_new_lemma_eh new_lemma_eh, + const datalog::t_predecessor_eh predecessor_eh, + const datalog::t_unfold_eh unfold_eh) { + ensure_engine(); + m_engine->add_callback(state, new_lemma_eh, predecessor_eh, unfold_eh); + } + + void add_constraint (expr *c, unsigned lvl){ + ensure_engine(); + m_engine->add_constraint(c, lvl); + } + private: /** Just reset all tables. */ - void reset_tables(); + void reset_tables(); void flush_add_rules(); @@ -614,4 +627,3 @@ namespace datalog { }; #endif /* DL_CONTEXT_H_ */ - diff --git a/src/muz/base/dl_engine_base.h b/src/muz/base/dl_engine_base.h index b9ac6e7b5..d6099e04c 100644 --- a/src/muz/base/dl_engine_base.h +++ b/src/muz/base/dl_engine_base.h @@ -25,9 +25,7 @@ Revision History: namespace datalog { enum DL_ENGINE { DATALOG_ENGINE, - PDR_ENGINE, SPACER_ENGINE, - QPDR_ENGINE, BMC_ENGINE, QBMC_ENGINE, TAB_ENGINE, @@ -36,6 +34,10 @@ namespace datalog { LAST_ENGINE }; + typedef void (*t_new_lemma_eh)(void *state, expr *lemma, unsigned level); + typedef void (*t_predecessor_eh)(void *state); + typedef void (*t_unfold_eh)(void *state); + class engine_base { ast_manager& m; std::string m_name; @@ -102,6 +104,15 @@ namespace datalog { virtual proof_ref get_proof() { return proof_ref(m.mk_asserted(m.mk_true()), m); } + virtual void add_callback(void *state, + const t_new_lemma_eh new_lemma_eh, + const t_predecessor_eh predecessor_eh, + const t_unfold_eh unfold_eh) { + throw default_exception(std::string("add_lemma_exchange_callbacks is not supported for ") + m_name); + } + virtual void add_constraint (expr *c, unsigned lvl){ + throw default_exception(std::string("add_constraint is not supported for ") + m_name); + } virtual void updt_params() {} virtual void cancel() {} virtual void cleanup() {} diff --git a/src/muz/base/fixedpoint_params.pyg b/src/muz/base/fp_params.pyg similarity index 57% rename from src/muz/base/fixedpoint_params.pyg rename to src/muz/base/fp_params.pyg index 8d6c750d5..c96a12ca2 100644 --- a/src/muz/base/fixedpoint_params.pyg +++ b/src/muz/base/fp_params.pyg @@ -1,37 +1,37 @@ -def_module_params('fixedpoint', +def_module_params('fp', description='fixedpoint parameters', export=True, params=(('timeout', UINT, UINT_MAX, 'set timeout'), - ('engine', SYMBOL, 'auto-config', - 'Select: auto-config, datalog, spacer, pdr, bmc'), - ('datalog.default_table', SYMBOL, 'sparse', + ('engine', SYMBOL, 'auto-config', + 'Select: auto-config, datalog, bmc, spacer'), + ('datalog.default_table', SYMBOL, 'sparse', 'default table implementation: sparse, hashtable, bitvector, interval'), - ('datalog.default_relation', SYMBOL, 'pentagon', + ('datalog.default_relation', SYMBOL, 'pentagon', 'default relation implementation: external_relation, pentagon'), - ('datalog.generate_explanations', BOOL, False, + ('datalog.generate_explanations', BOOL, False, 'produce explanations for produced facts when using the datalog engine'), - ('datalog.use_map_names', BOOL, True, + ('datalog.use_map_names', BOOL, True, "use names from map files when displaying tuples"), - ('datalog.magic_sets_for_queries', BOOL, False, + ('datalog.magic_sets_for_queries', BOOL, False, "magic set transformation will be used for queries"), - ('datalog.explanations_on_relation_level', BOOL, False, - 'if true, explanations are generated as history of each relation, ' + - 'rather than per fact (generate_explanations must be set to true for ' + + ('datalog.explanations_on_relation_level', BOOL, False, + 'if true, explanations are generated as history of each relation, ' + + 'rather than per fact (generate_explanations must be set to true for ' + 'this option to have any effect)'), - ('datalog.unbound_compressor', BOOL, True, - "auxiliary relations will be introduced to avoid unbound variables " + + ('datalog.unbound_compressor', BOOL, True, + "auxiliary relations will be introduced to avoid unbound variables " + "in rule heads"), - ('datalog.similarity_compressor', BOOL, True, - "rules that differ only in values of constants will be merged into " + + ('datalog.similarity_compressor', BOOL, True, + "rules that differ only in values of constants will be merged into " + "a single rule"), - ('datalog.similarity_compressor_threshold', UINT, 11, - "if similarity_compressor is on, this value determines how many " + + ('datalog.similarity_compressor_threshold', UINT, 11, + "if similarity_compressor is on, this value determines how many " + "similar rules there must be in order for them to be merged"), - ('datalog.all_or_nothing_deltas', BOOL, False, + ('datalog.all_or_nothing_deltas', BOOL, False, "compile rules so that it is enough for the delta relation in " + - "union and widening operations to determine only whether the " + + "union and widening operations to determine only whether the " + "updated relation was modified or not"), - ('datalog.compile_with_widening', BOOL, False, + ('datalog.compile_with_widening', BOOL, False, "widening will be used to compile recursive rules"), ('datalog.default_table_checked', BOOL, False, "if true, the default " + 'table will be default_table inside a wrapper that checks that its results ' + @@ -39,15 +39,15 @@ def_module_params('fixedpoint', ('datalog.default_table_checker', SYMBOL, 'null', "see default_table_checked"), ('datalog.check_relation',SYMBOL,'null', "name of default relation to check. " + "operations on the default relation will be verified using SMT solving"), - ('datalog.initial_restart_timeout', UINT, 0, - "length of saturation run before the first restart (in ms), " + + ('datalog.initial_restart_timeout', UINT, 0, + "length of saturation run before the first restart (in ms), " + "zero means no restarts"), - ('datalog.output_profile', BOOL, False, - "determines whether profile information should be " + + ('datalog.output_profile', BOOL, False, + "determines whether profile information should be " + "output when outputting Datalog rules or instructions"), - ('datalog.print.tuples', BOOL, True, + ('datalog.print.tuples', BOOL, True, "determines whether tuples for output predicates should be output"), - ('datalog.profile_timeout_milliseconds', UINT, 0, + ('datalog.profile_timeout_milliseconds', UINT, 0, "instructions and rules that took less than the threshold " + "will not be printed when printed the instruction/rule list"), ('datalog.dbg_fpr_nonempty_relation_signature', BOOL, False, @@ -56,139 +56,126 @@ def_module_params('fixedpoint', "table columns, if it would have been empty otherwise"), ('datalog.subsumption', BOOL, True, "if true, removes/filters predicates with total transitions"), - ('pdr.bfs_model_search', BOOL, True, - "use BFS strategy for expanding model search"), - ('pdr.farkas', BOOL, True, - "use lemma generator based on Farkas (for linear real arithmetic)"), ('generate_proof_trace', BOOL, False, "trace for 'sat' answer as proof object"), - ('pdr.flexible_trace', BOOL, False, - "allow PDR generate long counter-examples " + - "by extending candidate trace within search area"), - ('pdr.flexible_trace_depth', UINT, UINT_MAX, - 'Controls the depth (below the current level) at which flexible trace can be applied'), - ('pdr.use_model_generalizer', BOOL, False, - "use model for backwards propagation (instead of symbolic simulation)"), - ('pdr.validate_result', BOOL, False, + ('spacer.push_pob', BOOL, False, "push blocked pobs to higher level"), + ('spacer.push_pob_max_depth', UINT, UINT_MAX, + 'Maximum depth at which push_pob is enabled'), + ('validate', BOOL, False, "validate result (by proof checking or model checking)"), - ('pdr.simplify_formulas_pre', BOOL, False, - "simplify derived formulas before inductive propagation"), - ('pdr.simplify_formulas_post', BOOL, False, - "simplify derived formulas after inductive propagation"), - ('pdr.use_multicore_generalizer', BOOL, False, - "extract multiple cores for blocking states"), - ('pdr.use_inductive_generalizer', BOOL, True, + ('spacer.simplify_lemmas_pre', BOOL, False, + "simplify derived lemmas before inductive propagation"), + ('spacer.simplify_lemmas_post', BOOL, False, + "simplify derived lemmas after inductive propagation"), + ('spacer.use_inductive_generalizer', BOOL, True, "generalize lemmas using induction strengthening"), - ('pdr.use_arith_inductive_generalizer', BOOL, False, - "generalize lemmas using arithmetic heuristics for induction strengthening"), - ('pdr.use_convex_closure_generalizer', BOOL, False, - "generalize using convex closures of lemmas"), - ('pdr.use_convex_interior_generalizer', BOOL, False, - "generalize using convex interiors of lemmas"), - ('pdr.cache_mode', UINT, 0, "use no (0), symbolic (1) or explicit " + - "cache (2) for model search"), - ('pdr.inductive_reachability_check', BOOL, False, - "assume negation of the cube on the previous level when " + - "checking for reachability (not only during cube weakening)"), - ('pdr.max_num_contexts', UINT, 500, "maximal number of contexts to create"), - ('pdr.try_minimize_core', BOOL, False, - "try to reduce core size (before inductive minimization)"), - ('pdr.utvpi', BOOL, True, 'Enable UTVPI strategy'), - ('print_fixedpoint_extensions', BOOL, True, - "use SMT-LIB2 fixedpoint extensions, instead of pure SMT2, " + + ('spacer.max_num_contexts', UINT, 500, "maximal number of contexts to create"), + ('print_fixedpoint_extensions', BOOL, True, + "use SMT-LIB2 fixedpoint extensions, instead of pure SMT2, " + "when printing rules"), - ('print_low_level_smt2', BOOL, False, - "use (faster) low-level SMT2 printer (the printer is scalable " + + ('print_low_level_smt2', BOOL, False, + "use (faster) low-level SMT2 printer (the printer is scalable " + "but the result may not be as readable)"), - ('print_with_variable_declarations', BOOL, True, + ('print_with_variable_declarations', BOOL, True, "use variable declarations when displaying rules " + "(instead of attempting to use original names)"), ('print_answer', BOOL, False, 'print answer instance(s) to query'), - ('print_certificate', BOOL, False, + ('print_certificate', BOOL, False, 'print certificate for reachability or non-reachability'), - ('print_boogie_certificate', BOOL, False, + ('print_boogie_certificate', BOOL, False, 'print certificate for reachability or non-reachability using a ' + 'format understood by Boogie'), ('print_statistics', BOOL, False, 'print statistics'), - ('print_aig', SYMBOL, '', + ('print_aig', SYMBOL, '', 'Dump clauses in AIG text format (AAG) to the given file name'), - ('tab.selection', SYMBOL, 'weight', + ('tab.selection', SYMBOL, 'weight', 'selection method for tabular strategy: weight (default), first, var-use'), - ('xform.bit_blast', BOOL, False, + ('xform.bit_blast', BOOL, False, 'bit-blast bit-vectors'), - ('xform.magic', BOOL, False, + ('xform.magic', BOOL, False, "perform symbolic magic set transformation"), - ('xform.scale', BOOL, False, + ('xform.scale', BOOL, False, "add scaling variable to linear real arithmetic clauses"), ('xform.inline_linear', BOOL, True, "try linear inlining method"), ('xform.inline_eager', BOOL, True, "try eager inlining of rules"), - ('xform.inline_linear_branch', BOOL, False, + ('xform.inline_linear_branch', BOOL, False, "try linear inlining method with potential expansion"), ('xform.compress_unbound', BOOL, True, "compress tails with unbound variables"), ('xform.fix_unbound_vars', BOOL, False, "fix unbound variables in tail"), - ('xform.unfold_rules', UINT, 0, - "unfold rules statically using iterative squarring"), + ('xform.unfold_rules', UINT, 0, + "unfold rules statically using iterative squaring"), ('xform.slice', BOOL, True, "simplify clause set using slicing"), - ('xform.karr', BOOL, False, + ('xform.karr', BOOL, False, "Add linear invariants to clauses using Karr's method"), - ('spacer.use_eqclass', BOOL, False, "Generalizes equalities to equivalence classes"), - ('xform.transform_arrays', BOOL, False, + ('spacer.use_euf_gen', BOOL, False, 'Generalize lemmas and pobs using implied equalities'), + ('xform.transform_arrays', BOOL, False, "Rewrites arrays equalities and applies select over store"), - ('xform.instantiate_arrays', BOOL, False, + ('xform.instantiate_arrays', BOOL, False, "Transforms P(a) into P(i, a[i] a)"), - ('xform.instantiate_arrays.enforce', BOOL, False, + ('xform.instantiate_arrays.enforce', BOOL, False, "Transforms P(a) into P(i, a[i]), discards a from predicate"), - ('xform.instantiate_arrays.nb_quantifier', UINT, 1, + ('xform.instantiate_arrays.nb_quantifier', UINT, 1, "Gives the number of quantifiers per array"), - ('xform.instantiate_arrays.slice_technique', SYMBOL, "no-slicing", + ('xform.instantiate_arrays.slice_technique', SYMBOL, "no-slicing", "=> GetId(i) = i, => GetId(i) = true"), - ('xform.quantify_arrays', BOOL, False, + ('xform.quantify_arrays', BOOL, False, "create quantified Horn clauses from clauses with arrays"), - ('xform.instantiate_quantifiers', BOOL, False, + ('xform.instantiate_quantifiers', BOOL, False, "instantiate quantified Horn clauses using E-matching heuristic"), ('xform.coalesce_rules', BOOL, False, "coalesce rules"), ('xform.tail_simplifier_pve', BOOL, True, "propagate_variable_equivalences"), ('xform.subsumption_checker', BOOL, True, "Enable subsumption checker (no support for model conversion)"), ('xform.coi', BOOL, True, "use cone of influence simplification"), - ('spacer.order_children', UINT, 0, 'SPACER: order of enqueuing children in non-linear rules : 0 (original), 1 (reverse)'), - ('spacer.eager_reach_check', BOOL, True, 'SPACER: eagerly check if a query is reachable using reachability facts of predecessors'), + ('spacer.order_children', UINT, 0, 'SPACER: order of enqueuing children in non-linear rules : 0 (original), 1 (reverse), 2 (random)'), ('spacer.use_lemma_as_cti', BOOL, False, 'SPACER: use a lemma instead of a CTI in flexible_trace'), - ('spacer.reset_obligation_queue', BOOL, True, 'SPACER: reset obligation queue when entering a new level'), - ('spacer.init_reach_facts', BOOL, True, 'SPACER: initialize reachability facts with false'), + ('spacer.reset_pob_queue', BOOL, True, 'SPACER: reset pob obligation queue when entering a new level'), ('spacer.use_array_eq_generalizer', BOOL, True, 'SPACER: attempt to generalize lemmas with array equalities'), - ('spacer.use_derivations', BOOL, True, 'SPACER: using derivation mechanism to cache intermediate results for non-linear rules'), - ('xform.array_blast', BOOL, False, "try to eliminate local array terms using Ackermannization -- some array terms may remain"), - ('xform.array_blast_full', BOOL, False, "eliminate all local array variables by QE"), - ('spacer.skip_propagate', BOOL, False, "Skip propagate/pushing phase. Turns PDR into a BMC that returns either reachable or unknown"), + ('spacer.use_derivations', BOOL, True, 'SPACER: using derivation mechanism to cache intermediate results for non-linear rules'), + ('xform.array_blast', BOOL, False, "try to eliminate local array terms using Ackermannization -- some array terms may remain"), + ('xform.array_blast_full', BOOL, False, "eliminate all local array variables by QE"), + ('spacer.propagate', BOOL, True, 'Enable propagate/pushing phase'), ('spacer.max_level', UINT, UINT_MAX, "Maximum level to explore"), ('spacer.elim_aux', BOOL, True, "Eliminate auxiliary variables in reachability facts"), - ('spacer.reach_as_init', BOOL, True, "Extend initial rules with computed reachability facts"), ('spacer.blast_term_ite', BOOL, True, "Expand non-Boolean ite-terms"), - ('spacer.nondet_tie_break', BOOL, False, "Break ties in obligation queue non-deterministically"), ('spacer.reach_dnf', BOOL, True, "Restrict reachability facts to DNF"), ('bmc.linear_unrolling_depth', UINT, UINT_MAX, "Maximal level to explore"), - ('spacer.split_farkas_literals', BOOL, False, "Split Farkas literals"), - ('spacer.native_mbp', BOOL, False, "Use native mbp of Z3"), + ('spacer.iuc.split_farkas_literals', BOOL, False, "Split Farkas literals"), + ('spacer.native_mbp', BOOL, True, "Use native mbp of Z3"), ('spacer.eq_prop', BOOL, True, "Enable equality and bound propagation in arithmetic"), ('spacer.weak_abs', BOOL, True, "Weak abstraction"), ('spacer.restarts', BOOL, False, "Enable reseting obligation queue"), ('spacer.restart_initial_threshold', UINT, 10, "Intial threshold for restarts"), ('spacer.random_seed', UINT, 0, "Random seed to be used by SMT solver"), - ('spacer.ground_cti', BOOL, True, "Require CTI to be ground"), - ('spacer.vs.dump_benchmarks', BOOL, False, 'dump benchmarks in virtual solver'), - ('spacer.vs.dump_min_time', DOUBLE, 5.0, 'min time to dump benchmark'), - ('spacer.vs.recheck', BOOL, False, 're-check locally during benchmark dumping'), - ('spacer.mbqi', BOOL, True, 'use model-based quantifier instantiation'), + + ('spacer.mbqi', BOOL, True, 'Enable mbqi'), ('spacer.keep_proxy', BOOL, True, 'keep proxy variables (internal parameter)'), - ('spacer.instantiate', BOOL, True, 'instantiate quantified lemmas'), - ('spacer.qlemmas', BOOL, True, 'allow quantified lemmas in frames'), - ('spacer.new_unsat_core', BOOL, True, 'use the new implementation of unsat-core-generation'), - ('spacer.minimize_unsat_core', BOOL, False, 'compute unsat-core by min-cut'), - ('spacer.farkas_optimized', BOOL, True, 'use the optimized farkas plugin, which performs gaussian elimination'), - ('spacer.farkas_a_const', BOOL, True, 'if the unoptimized farkas plugin is used, use the constants from A while constructing unsat_cores'), - ('spacer.lemma_sanity_check', BOOL, False, 'check during generalization whether lemma is actually correct'), - ('spacer.reuse_pobs', BOOL, True, 'reuse POBs'), - ('spacer.simplify_pob', BOOL, False, 'simplify POBs by removing redundant constraints') + ('spacer.q3', BOOL, True, 'Allow quantified lemmas in frames'), + ('spacer.q3.instantiate', BOOL, True, 'Instantiate quantified lemmas'), + ('spacer.iuc', UINT, 1, + '0 = use old implementation of unsat-core-generation, ' + + '1 = use new implementation of IUC generation, ' + + '2 = use new implementation of IUC + min-cut optimization'), + ('spacer.iuc.arith', UINT, 1, + '0 = use simple Farkas plugin, ' + + '1 = use simple Farkas plugin with constant from other partition (like old unsat-core-generation),' + + '2 = use Gaussian elimination optimization (broken), 3 = use additive IUC plugin'), + ('spacer.iuc.old_hyp_reducer', BOOL, False, 'use old hyp reducer instead of new implementation, for debugging only'), + ('spacer.validate_lemmas', BOOL, False, 'Validate each lemma after generalization'), + ('spacer.reuse_pobs', BOOL, True, 'Reuse pobs'), + ('spacer.ground_pobs', BOOL, True, 'Ground pobs by using values from a model'), + ('spacer.iuc.print_farkas_stats', BOOL, False, 'prints for each proof how many Farkas lemmas it contains and how many of these participate in the cut (for debugging)'), + ('spacer.iuc.debug_proof', BOOL, False, 'prints proof used by unsat-core-learner for debugging purposes (debugging)'), + ('spacer.simplify_pob', BOOL, False, 'simplify pobs by removing redundant constraints'), + ('spacer.q3.use_qgen', BOOL, False, 'use quantified lemma generalizer'), + ('spacer.q3.qgen.normalize', BOOL, True, 'normalize cube before quantified generalization'), + ('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'), + ('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"), + ('spacer.min_level', UINT, 0, 'Minimal level to explore'), + ('spacer.print_json', SYMBOL, '', 'Print pobs tree in JSON format to a given file'), + ('spacer.ctp', BOOL, True, 'Enable counterexample-to-pushing'), + ('spacer.use_inc_clause', BOOL, True, 'Use incremental clause to represent trans'), + ('spacer.dump_benchmarks', BOOL, False, 'Dump SMT queries as benchmarks'), + ('spacer.dump_threshold', DOUBLE, 5.0, 'Threshold in seconds on dumping benchmarks'), + ('spacer.gpdr', BOOL, False, 'Use GPDR solving strategy for non-linear CHC'), + ('spacer.gpdr.bfs', BOOL, True, 'Use BFS exploration strategy for expanding model search'), + )) - - - diff --git a/src/muz/base/rule_properties.cpp b/src/muz/base/rule_properties.cpp index 21317a07c..315765acc 100644 --- a/src/muz/base/rule_properties.cpp +++ b/src/muz/base/rule_properties.cpp @@ -146,12 +146,10 @@ void rule_properties::check_existential_tail() { else if (is_quantifier(e)) { tocheck.push_back(to_quantifier(e)->get_expr()); } - else if ((m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) && - m.is_true(e1)) { + else if (m.is_eq(e, e1, e2) && m.is_true(e1)) { todo.push_back(e2); } - else if ((m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) && - m.is_true(e2)) { + else if (m.is_eq(e, e1, e2) && m.is_true(e2)) { todo.push_back(e1); } else { diff --git a/src/muz/bmc/dl_bmc_engine.cpp b/src/muz/bmc/dl_bmc_engine.cpp index 56b7e449d..97432f544 100644 --- a/src/muz/bmc/dl_bmc_engine.cpp +++ b/src/muz/bmc/dl_bmc_engine.cpp @@ -33,7 +33,7 @@ Revision History: #include "muz/transforms/dl_mk_rule_inliner.h" #include "ast/scoped_proof.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { @@ -228,10 +228,9 @@ namespace datalog { expr_ref eval_q(model_ref& model, func_decl* f, unsigned i) { func_decl_ref fn = mk_q_func_decl(f); - expr_ref t(m), result(m); + expr_ref t(m); t = m.mk_app(mk_q_func_decl(f).get(), mk_q_num(i)); - model->eval(t, result); - return result; + return (*model)(t); } expr_ref eval_q(model_ref& model, expr* t, unsigned i) { @@ -240,8 +239,7 @@ namespace datalog { num = mk_q_num(i); expr* nums[1] = { num }; vs(t, 1, nums, tmp); - model->eval(tmp, result); - return result; + return (*model)(tmp); } lbool get_model() { @@ -258,7 +256,7 @@ namespace datalog { func_decl* pred = b.m_query_pred; dl_decl_util util(m); T = m.mk_const(symbol("T"), mk_index_sort()); - md->eval(T, vl); + vl = (*md)(T); VERIFY (m_bv.is_numeral(vl, num, bv_size)); SASSERT(num.is_unsigned()); level = num.get_unsigned(); @@ -505,8 +503,7 @@ namespace datalog { func_decl_ref rule_i = mk_level_rule(pred, i, level); TRACE("bmc", rls[i]->display(b.m_ctx, tout << "Checking rule " << mk_pp(rule_i, m) << " ");); prop_r = m.mk_app(rule_i, prop->get_num_args(), prop->get_args()); - md->eval(prop_r, prop_v); - if (m.is_true(prop_v)) { + if (md->is_true(prop_r)) { r = rls[i]; break; } @@ -527,8 +524,7 @@ namespace datalog { return pr; } for (unsigned j = 0; j < sub.size(); ++j) { - md->eval(sub[j].get(), tmp); - sub[j] = tmp; + sub[j] = (*md)(sub[j].get()); } svector > positions; @@ -1062,8 +1058,7 @@ namespace datalog { return pr; } for (unsigned j = 0; j < sub.size(); ++j) { - md->eval(sub[j].get(), tmp); - sub[j] = tmp; + sub[j] = (*md)(sub.get(j)); } rule_ref rl(b.m_ctx.get_rule_manager()); rl = rules[i]; @@ -1116,7 +1111,7 @@ namespace datalog { bool check_model(model_ref& md, expr* trace) { expr_ref trace_val(m), eq(m); - md->eval(trace, trace_val); + trace_val = (*md)(trace); eq = m.mk_eq(trace, trace_val); b.m_solver.push(); b.m_solver.assert_expr(eq); @@ -1135,8 +1130,8 @@ namespace datalog { void mk_answer(model_ref& md, expr_ref& trace, expr_ref& path) { IF_VERBOSE(2, model_smt2_pp(verbose_stream(), m, *md, 0);); - md->eval(trace, trace); - md->eval(path, path); + trace = (*md)(trace); + path = (*md)(path); IF_VERBOSE(2, verbose_stream() << mk_pp(trace, m) << "\n"; for (unsigned i = 0; i < b.m_solver.size(); ++i) { verbose_stream() << mk_pp(b.m_solver.get_formula(i), m) << "\n"; diff --git a/src/muz/fp/CMakeLists.txt b/src/muz/fp/CMakeLists.txt index 41262813a..4837df81b 100644 --- a/src/muz/fp/CMakeLists.txt +++ b/src/muz/fp/CMakeLists.txt @@ -9,7 +9,6 @@ z3_add_component(fp clp ddnf muz - pdr rel spacer tab diff --git a/src/muz/fp/dl_cmds.cpp b/src/muz/fp/dl_cmds.cpp index ba12c849c..4605826ba 100644 --- a/src/muz/fp/dl_cmds.cpp +++ b/src/muz/fp/dl_cmds.cpp @@ -30,23 +30,23 @@ Notes: #include "util/scoped_ctrl_c.h" #include "util/scoped_timer.h" #include "util/trail.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include struct dl_context { smt_params m_fparams; params_ref m_params_ref; - fixedpoint_params m_params; + fp_params m_params; cmd_context & m_cmd; datalog::register_engine m_register_engine; dl_collected_cmds* m_collected_cmds; unsigned m_ref_count; datalog::dl_decl_plugin* m_decl_plugin; - scoped_ptr m_context; + scoped_ptr m_context; trail_stack m_trail; - fixedpoint_params const& get_params() { + fp_params const& get_params() { init(); return m_context->get_params(); } @@ -58,18 +58,18 @@ struct dl_context { m_ref_count(0), m_decl_plugin(nullptr), m_trail(*this) {} - + void inc_ref() { ++m_ref_count; } - + void dec_ref() { --m_ref_count; if (0 == m_ref_count) { dealloc(this); } } - + void init() { ast_manager& m = m_cmd.m(); if (!m_context) { @@ -83,10 +83,10 @@ struct dl_context { else { m_decl_plugin = alloc(datalog::dl_decl_plugin); m.register_plugin(symbol("datalog_relation"), m_decl_plugin); - } + } } } - + void reset() { m_context = nullptr; } @@ -97,9 +97,9 @@ struct dl_context { m_trail.push(push_back_vector(m_collected_cmds->m_rels)); } dlctx().register_predicate(pred, false); - dlctx().set_predicate_representation(pred, num_kinds, kinds); + dlctx().set_predicate_representation(pred, num_kinds, kinds); } - + void add_rule(expr * rule, symbol const& name, unsigned bound) { init(); if (m_collected_cmds) { @@ -112,7 +112,7 @@ struct dl_context { else { m_context->add_rule(rule, name, bound); } - } + } bool collect_query(func_decl* q) { if (m_collected_cmds) { @@ -127,7 +127,7 @@ struct dl_context { m_collected_cmds->m_queries.push_back(qr); m_trail.push(push_back_vector(m_collected_cmds->m_queries)); return true; - } + } else { return false; } @@ -142,7 +142,7 @@ struct dl_context { m_trail.pop_scope(1); dlctx().pop(); } - + datalog::context & dlctx() { init(); return *m_context; @@ -162,7 +162,7 @@ class dl_rule_cmd : public cmd { public: dl_rule_cmd(dl_context * dl_ctx): cmd("rule"), - m_dl_ctx(dl_ctx), + m_dl_ctx(dl_ctx), m_arg_idx(0), m_t(nullptr), m_bound(UINT_MAX) {} @@ -210,7 +210,7 @@ public: } char const * get_usage() const override { return "predicate"; } char const * get_main_descr() const override { - return "pose a query to a predicate based on the Horn rules."; + return "pose a query to a predicate based on the Horn rules."; } cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { @@ -243,9 +243,9 @@ public: return; } datalog::context& dlctx = m_dl_ctx->dlctx(); - set_background(ctx); + set_background(ctx); dlctx.updt_params(m_params); - unsigned timeout = m_dl_ctx->get_params().timeout(); + unsigned timeout = m_dl_ctx->get_params().timeout(); cancel_eh eh(ctx.m().limit()); bool query_exn = false; lbool status = l_undef; @@ -271,12 +271,12 @@ public: ctx.regular_stream() << "unsat\n"; print_certificate(ctx); break; - case l_true: + case l_true: ctx.regular_stream() << "sat\n"; print_answer(ctx); print_certificate(ctx); break; - case l_undef: + case l_undef: if (dlctx.get_status() == datalog::BOUNDED){ ctx.regular_stream() << "bounded\n"; print_certificate(ctx); @@ -287,7 +287,7 @@ public: case datalog::INPUT_ERROR: ctx.regular_stream() << "input error\n"; break; - + case datalog::MEMOUT: ctx.regular_stream() << "memory bounds exceeded\n"; break; @@ -295,12 +295,12 @@ public: case datalog::TIMEOUT: ctx.regular_stream() << "timeout\n"; break; - + case datalog::APPROX: ctx.regular_stream() << "approximated relations\n"; break; - case datalog::OK: + case datalog::OK: (void)query_exn; SASSERT(query_exn); break; @@ -324,7 +324,7 @@ public: void init_pdescrs(cmd_context & ctx, param_descrs & p) override { m_dl_ctx->dlctx().collect_params(p); } - + private: void set_background(cmd_context& ctx) { @@ -356,8 +356,8 @@ private: statistics st; datalog::context& dlctx = m_dl_ctx->dlctx(); dlctx.collect_statistics(st); - st.update("time", ctx.get_seconds()); - st.display_smt2(ctx.regular_stream()); + st.update("time", ctx.get_seconds()); + st.display_smt2(ctx.regular_stream()); } } @@ -391,8 +391,8 @@ public: void prepare(cmd_context & ctx) override { ctx.m(); // ensure manager is initialized. - m_arg_idx = 0; - m_query_arg_idx = 0; + m_arg_idx = 0; + m_query_arg_idx = 0; m_domain.reset(); m_kinds.reset(); } @@ -443,21 +443,21 @@ public: m_arg_idx(0), m_dl_ctx(dl_ctx) {} - + char const * get_usage() const override { return " "; } char const * get_descr(cmd_context & ctx) const override { return "declare constant as variable"; } unsigned get_arity() const override { return 2; } void prepare(cmd_context & ctx) override { ctx.m(); // ensure manager is initialized. - m_arg_idx = 0; + m_arg_idx = 0; } cmd_arg_kind next_arg_kind(cmd_context & ctx) const override { SASSERT(m_arg_idx <= 1); if (m_arg_idx == 0) { - return CPK_SYMBOL; + return CPK_SYMBOL; } - return CPK_SORT; + return CPK_SORT; } void set_next_arg(cmd_context & ctx, sort* s) override { @@ -466,7 +466,7 @@ public: } void set_next_arg(cmd_context & ctx, symbol const & s) override { - m_var_name = s; + m_var_name = s; ++m_arg_idx; } @@ -523,7 +523,7 @@ static void install_dl_cmds_aux(cmd_context& ctx, dl_collected_cmds* collected_c ctx.insert(alloc(dl_query_cmd, dl_ctx)); ctx.insert(alloc(dl_declare_rel_cmd, dl_ctx)); ctx.insert(alloc(dl_declare_var_cmd, dl_ctx)); - ctx.insert(alloc(dl_push_cmd, dl_ctx)); + ctx.insert(alloc(dl_push_cmd, dl_ctx)); ctx.insert(alloc(dl_pop_cmd, dl_ctx)); } diff --git a/src/muz/fp/dl_register_engine.cpp b/src/muz/fp/dl_register_engine.cpp index a2270d774..28a2a1c5e 100644 --- a/src/muz/fp/dl_register_engine.cpp +++ b/src/muz/fp/dl_register_engine.cpp @@ -21,7 +21,6 @@ Revision History: #include "muz/clp/clp_context.h" #include "muz/tab/tab_context.h" #include "muz/rel/rel_context.h" -#include "muz/pdr/pdr_dl_interface.h" #include "muz/ddnf/ddnf.h" #include "muz/spacer/spacer_dl_interface.h" @@ -30,9 +29,6 @@ namespace datalog { engine_base* register_engine::mk_engine(DL_ENGINE engine_type) { switch(engine_type) { - case PDR_ENGINE: - case QPDR_ENGINE: - return alloc(pdr::dl_interface, *m_ctx); case SPACER_ENGINE: return alloc(spacer::dl_interface, *m_ctx); case DATALOG_ENGINE: diff --git a/src/muz/fp/horn_tactic.cpp b/src/muz/fp/horn_tactic.cpp index 4843a2623..eef95915d 100644 --- a/src/muz/fp/horn_tactic.cpp +++ b/src/muz/fp/horn_tactic.cpp @@ -27,7 +27,7 @@ Revision History: #include "muz/transforms/dl_mk_slice.h" #include "tactic/generic_model_converter.h" #include "muz/transforms/dl_transforms.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include "ast/ast_util.h" #include "ast/rewriter/var_subst.h" @@ -71,7 +71,7 @@ class horn_tactic : public tactic { f = to_quantifier(f)->get_expr(); } else if (is_exists(f) && !is_positive) { - f = to_quantifier(f)->get_expr(); + f = to_quantifier(f)->get_expr(); } else if (m.is_not(f, e)) { is_positive = !is_positive; @@ -84,7 +84,7 @@ class horn_tactic : public tactic { if (!is_positive) { f = m.mk_not(f); } - + } bool is_predicate(expr* a) { @@ -144,7 +144,7 @@ class horn_tactic : public tactic { expr* a = nullptr, *a1 = nullptr; flatten_or(tmp, args); for (unsigned i = 0; i < args.size(); ++i) { - a = args[i].get(); + a = args[i].get(); check_predicate(mark, a); if (m.is_not(a, a1)) { body.push_back(a1); @@ -176,13 +176,13 @@ class horn_tactic : public tactic { return expr_ref(m.mk_implies(body, head), m); } - void operator()(goal_ref const & g, + void operator()(goal_ref const & g, goal_ref_buffer & result) { SASSERT(g->is_well_sorted()); tactic_report report("horn", *g); bool produce_proofs = g->proofs_enabled(); - if (produce_proofs) { + if (produce_proofs) { if (!m_ctx.generate_proof_trace()) { params_ref params = m_ctx.get_params().p; params.set_bool("generate_proof_trace", true); @@ -208,7 +208,7 @@ class horn_tactic : public tactic { case IS_QUERY: queries.push_back(f); break; - default: + default: msg << "formula is not in Horn fragment: " << mk_pp(g->form(i), m) << "\n"; TRACE("horn", tout << msg.str();); throw tactic_exception(msg.str().c_str()); @@ -243,10 +243,10 @@ class horn_tactic : public tactic { g->set(mc.get()); } - void verify(expr* q, + void verify(expr* q, goal_ref const& g, - goal_ref_buffer & result, - model_converter_ref & mc, + goal_ref_buffer & result, + model_converter_ref & mc, proof_converter_ref & pc) { lbool is_reachable = l_undef; @@ -275,9 +275,9 @@ class horn_tactic : public tactic { else { g->assert_expr(m.mk_false()); } - break; + break; } - case l_false: { + case l_false: { // goal is sat g->reset(); if (produce_models) { @@ -290,11 +290,11 @@ class horn_tactic : public tactic { mc = mc2; } } - break; + break; } - case l_undef: + case l_undef: // subgoal is unchanged. - break; + break; } TRACE("horn", g->display(tout);); SASSERT(g->is_well_sorted()); @@ -314,20 +314,20 @@ class horn_tactic : public tactic { } } - void simplify(expr* q, + void simplify(expr* q, goal_ref const& g, - goal_ref_buffer & result, - model_converter_ref & mc, + goal_ref_buffer & result, + model_converter_ref & mc, proof_converter_ref & pc) { - expr_ref fml(m); + expr_ref fml(m); func_decl* query_pred = to_app(q)->get_decl(); m_ctx.set_output_predicate(query_pred); m_ctx.get_rules(); // flush adding rules. apply_default_transformation(m_ctx); - + if (m_ctx.xform_slice()) { datalog::rule_transformer transformer(m_ctx); datalog::mk_slice* slice = alloc(datalog::mk_slice, m_ctx); @@ -351,7 +351,7 @@ class horn_tactic : public tactic { g->assert_expr(fml); } } - + }; bool m_is_simplify; @@ -368,7 +368,7 @@ public: tactic * translate(ast_manager & m) override { return alloc(horn_tactic, m_is_simplify, m, m_params); } - + ~horn_tactic() override { dealloc(m_imp); } @@ -378,16 +378,16 @@ public: m_imp->updt_params(p); } - + void collect_param_descrs(param_descrs & r) override { m_imp->collect_param_descrs(r); } - - void operator()(goal_ref const & in, + + void operator()(goal_ref const & in, goal_ref_buffer & result) override { (*m_imp)(in, result); } - + void collect_statistics(statistics & st) const override { m_imp->collect_statistics(st); st.copy(m_stats); @@ -397,15 +397,15 @@ public: m_stats.reset(); m_imp->reset_statistics(); } - + void cleanup() override { ast_manager & m = m_imp->m; m_imp->collect_statistics(m_stats); dealloc(m_imp); m_imp = alloc(imp, m_is_simplify, m, m_params); - + } - + }; @@ -416,4 +416,3 @@ tactic * mk_horn_tactic(ast_manager & m, params_ref const & p) { tactic * mk_horn_simplify_tactic(ast_manager & m, params_ref const & p) { return clean(alloc(horn_tactic, true, m, p)); } - diff --git a/src/muz/pdr/CMakeLists.txt b/src/muz/pdr/CMakeLists.txt deleted file mode 100644 index ca2992b97..000000000 --- a/src/muz/pdr/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -z3_add_component(pdr - SOURCES - pdr_closure.cpp - pdr_context.cpp - pdr_dl_interface.cpp - pdr_farkas_learner.cpp - pdr_generalizers.cpp - pdr_manager.cpp - pdr_prop_solver.cpp - pdr_reachable_cache.cpp - pdr_smt_context_manager.cpp - pdr_sym_mux.cpp - pdr_util.cpp - COMPONENT_DEPENDENCIES - arith_tactics - core_tactics - muz - smt_tactic - transforms -) diff --git a/src/muz/pdr/pdr_closure.cpp b/src/muz/pdr/pdr_closure.cpp deleted file mode 100644 index 82caf285b..000000000 --- a/src/muz/pdr/pdr_closure.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/*++ -Copyright (c) 2013 Microsoft Corporation - -Module Name: - - pdr_closure.cpp - -Abstract: - - Utility functions for computing closures. - -Author: - - Nikolaj Bjorner (nbjorner) 2013-9-1. - -Revision History: - ---*/ - -#include "muz/pdr/pdr_closure.h" -#include "muz/pdr/pdr_context.h" -#include "ast/rewriter/expr_safe_replace.h" -#include "ast/ast_util.h" - -namespace pdr { - - expr_ref scaler::operator()(expr* e, expr* k, obj_map* translate) { - m_cache[0].reset(); - m_cache[1].reset(); - m_translate = translate; - m_k = k; - return scale(e, false); - } - - expr_ref scaler::scale(expr* e, bool is_mul) { - expr* r; - if (m_cache[is_mul].find(e, r)) { - return expr_ref(r, m); - } - if (!is_app(e)) { - return expr_ref(e, m); - } - app* ap = to_app(e); - if (m_translate && m_translate->find(ap->get_decl(), r)) { - return expr_ref(r, m); - } - if (!is_mul && a.is_numeral(e)) { - return expr_ref(a.mk_mul(m_k, e), m); - } - expr_ref_vector args(m); - bool is_mul_rec = is_mul || a.is_mul(e); - for (unsigned i = 0; i < ap->get_num_args(); ++i) { - args.push_back(scale(ap->get_arg(i), is_mul_rec)); - } - expr_ref result(m); - result = m.mk_app(ap->get_decl(), args.size(), args.c_ptr()); - m_cache[is_mul].insert(e, result); - return result; - } - - expr_ref scaler::undo_k(expr* e, expr* k) { - expr_safe_replace sub(m); - th_rewriter rw(m); - expr_ref result(e, m); - sub.insert(k, a.mk_numeral(rational(1), false)); - sub(result); - rw(result); - return result; - } - - - closure::closure(pred_transformer& p, bool is_closure): - m(p.get_manager()), m_pt(p), a(m), - m_is_closure(is_closure), m_sigma(m), m_trail(m) {} - - - void closure::add_variables(unsigned num_vars, expr_ref_vector& fmls) { - manager& pm = m_pt.get_pdr_manager(); - SASSERT(num_vars > 0); - while (m_vars.size() < num_vars) { - m_vars.resize(m_vars.size()+1); - m_sigma.push_back(m.mk_fresh_const("sigma", a.mk_real())); - } - - unsigned sz = m_pt.sig_size(); - - for (unsigned i = 0; i < sz; ++i) { - expr* var; - ptr_vector vars; - func_decl* fn0 = m_pt.sig(i); - func_decl* fn1 = pm.o2n(fn0, 0); - sort* srt = fn0->get_range(); - if (a.is_int_real(srt)) { - for (unsigned j = 0; j < num_vars; ++j) { - if (!m_vars[j].find(fn1, var)) { - var = m.mk_fresh_const(fn1->get_name().str().c_str(), srt); - m_trail.push_back(var); - m_vars[j].insert(fn1, var); - } - vars.push_back(var); - } - fmls.push_back(m.mk_eq(m.mk_const(fn1), a.mk_add(num_vars, vars.c_ptr()))); - } - } - if (m_is_closure) { - for (unsigned i = 0; i < num_vars; ++i) { - fmls.push_back(a.mk_ge(m_sigma[i].get(), a.mk_numeral(rational(0), a.mk_real()))); - } - } - else { - // is interior: - for (unsigned i = 0; i < num_vars; ++i) { - fmls.push_back(a.mk_gt(m_sigma[i].get(), a.mk_numeral(rational(0), a.mk_real()))); - } - } - fmls.push_back(m.mk_eq(a.mk_numeral(rational(1), a.mk_real()), a.mk_add(num_vars, m_sigma.c_ptr()))); - } - - expr_ref closure::close_fml(expr* e) { - expr* e0, *e1, *e2; - expr_ref result(m); - if (a.is_lt(e, e1, e2)) { - result = a.mk_le(e1, e2); - } - else if (a.is_gt(e, e1, e2)) { - result = a.mk_ge(e1, e2); - } - else if (m.is_not(e, e0) && a.is_ge(e0, e1, e2)) { - result = a.mk_le(e1, e2); - } - else if (m.is_not(e, e0) && a.is_le(e0, e1, e2)) { - result = a.mk_ge(e1, e2); - } - else if (a.is_ge(e) || a.is_le(e) || m.is_eq(e) || - (m.is_not(e, e0) && (a.is_gt(e0) || a.is_lt(e0)))) { - result = e; - } - else { - IF_VERBOSE(1, verbose_stream() << "Cannot close: " << mk_pp(e, m) << "\n";); - result = m.mk_true(); - } - return result; - } - - expr_ref closure::close_conjunction(expr* fml) { - expr_ref_vector fmls(m); - flatten_and(fml, fmls); - for (unsigned i = 0; i < fmls.size(); ++i) { - fmls[i] = close_fml(fmls[i].get()); - } - return expr_ref(mk_and(fmls), m); - } - - expr_ref closure::relax(unsigned i, expr* fml) { - scaler sc(m); - expr_ref result = sc(fml, m_sigma[i].get(), &m_vars[i]); - return close_conjunction(result); - } - - expr_ref closure::operator()(expr_ref_vector const& As) { - if (As.empty()) { - return expr_ref(m.mk_false(), m); - } - if (As.size() == 1) { - return expr_ref(As[0], m); - } - expr_ref_vector fmls(m); - expr_ref B(m); - add_variables(As.size(), fmls); - for (unsigned i = 0; i < As.size(); ++i) { - fmls.push_back(relax(i, As[i])); - } - B = mk_and(fmls); - return B; - } - -} diff --git a/src/muz/pdr/pdr_closure.h b/src/muz/pdr/pdr_closure.h deleted file mode 100644 index c41e83389..000000000 --- a/src/muz/pdr/pdr_closure.h +++ /dev/null @@ -1,67 +0,0 @@ -/*++ -Copyright (c) 2013 Microsoft Corporation - -Module Name: - - pdr_closure.h - -Abstract: - - Utility functions for computing closures. - -Author: - - Nikolaj Bjorner (nbjorner) 2013-9-1. - -Revision History: - ---*/ - -#ifndef PDR_CLOSURE_H_ -#define PDR_CLOSURE_H_ - -#include "ast/arith_decl_plugin.h" - -namespace pdr { - - // Arithmetic scaling functor. - // Variables are replaced using - // m_translate. Constants are replaced by - // multiplication with a variable 'k' (scale factor). - class scaler { - ast_manager& m; - arith_util a; - obj_map m_cache[2]; - expr* m_k; - obj_map* m_translate; - public: - scaler(ast_manager& m): m(m), a(m), m_translate(nullptr) {} - expr_ref operator()(expr* e, expr* k, obj_map* translate = nullptr); - expr_ref undo_k(expr* e, expr* k); - private: - expr_ref scale(expr* e, bool is_mul); - }; - - class pred_transformer; - - class closure { - ast_manager& m; - pred_transformer& m_pt; - arith_util a; - bool m_is_closure; - expr_ref_vector m_sigma; - expr_ref_vector m_trail; - vector > m_vars; - - expr_ref relax(unsigned i, expr* fml); - expr_ref close_conjunction(expr* fml); - expr_ref close_fml(expr* fml); - void add_variables(unsigned num_vars, expr_ref_vector& fmls); - public: - closure(pred_transformer& pt, bool is_closure); - expr_ref operator()(expr_ref_vector const& As); - - }; -} - -#endif diff --git a/src/muz/pdr/pdr_context.cpp b/src/muz/pdr/pdr_context.cpp deleted file mode 100644 index 77b79ba04..000000000 --- a/src/muz/pdr/pdr_context.cpp +++ /dev/null @@ -1,2418 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_context.cpp - -Abstract: - - PDR predicate transformers and search context. - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-20. - -Revision History: - - Based on pdr_dl.cpp by - Krystof Hoder (t-khoder) 2011-9-19. - -Notes: - - ---*/ - - -#include -#include "muz/base/dl_util.h" -#include "ast/rewriter/rewriter.h" -#include "ast/rewriter/rewriter_def.h" -#include "ast/rewriter/var_subst.h" -#include "util/util.h" -#include "muz/pdr/pdr_prop_solver.h" -#include "muz/pdr/pdr_context.h" -#include "muz/pdr/pdr_generalizers.h" -#include "ast/for_each_expr.h" -#include "muz/base/dl_rule_set.h" -#include "smt/tactic/unit_subsumption_tactic.h" -#include "model/model_smt2_pp.h" -#include "muz/transforms/dl_mk_rule_inliner.h" -#include "ast/ast_smt2_pp.h" -#include "qe/qe_lite.h" -#include "ast/ast_ll_pp.h" -#include "ast/proofs/proof_checker.h" -#include "smt/smt_value_sort.h" -#include "muz/base/dl_boogie_proof.h" -#include "ast/scoped_proof.h" -#include "tactic/core/blast_term_ite_tactic.h" -#include "model/model_implicant.h" -#include "ast/rewriter/expr_safe_replace.h" -#include "ast/ast_util.h" - -namespace pdr { - - - static const unsigned infty_level = UINT_MAX; - - static bool is_infty_level(unsigned lvl) { return lvl == infty_level; } - - static unsigned next_level(unsigned lvl) { return is_infty_level(lvl)?lvl:(lvl+1); } - - struct pp_level { - unsigned m_level; - pp_level(unsigned l): m_level(l) {} - }; - - static std::ostream& operator<<(std::ostream& out, pp_level const& p) { - if (is_infty_level(p.m_level)) { - return out << "oo"; - } - else { - return out << p.m_level; - } - } - - // ---------------- - // pred_tansformer - - pred_transformer::pred_transformer(context& ctx, manager& pm, func_decl* head): - pm(pm), m(pm.get_manager()), - ctx(ctx), m_head(head, m), - m_sig(m), m_solver(pm, head->get_name()), - m_invariants(m), m_transition(m), m_initial_state(m), - m_reachable(pm, (datalog::PDR_CACHE_MODE)ctx.get_params().pdr_cache_mode()) {} - - pred_transformer::~pred_transformer() { - rule2inst::iterator it2 = m_rule2inst.begin(), end2 = m_rule2inst.end(); - for (; it2 != end2; ++it2) { - dealloc(it2->m_value); - } - rule2expr::iterator it3 = m_rule2transition.begin(), end3 = m_rule2transition.end(); - for (; it3 != end3; ++it3) { - m.dec_ref(it3->m_value); - } - } - - std::ostream& pred_transformer::display(std::ostream& out) const { - if (!rules().empty()) out << "rules\n"; - datalog::rule_manager& rm = ctx.get_context().get_rule_manager(); - for (unsigned i = 0; i < rules().size(); ++i) { - rm.display_smt2(*rules()[i], out) << "\n"; - } - out << "transition\n" << mk_pp(transition(), m) << "\n"; - return out; - } - - void pred_transformer::collect_statistics(statistics& st) const { - m_solver.collect_statistics(st); - m_reachable.collect_statistics(st); - st.update("PDR num propagations", m_stats.m_num_propagations); - unsigned np = m_invariants.size(); - for (unsigned i = 0; i < m_levels.size(); ++i) { - np += m_levels[i].size(); - } - st.update("PDR num properties", np); - } - - void pred_transformer::reset_statistics() { - m_solver.reset_statistics(); - m_reachable.reset_statistics(); - m_stats.reset(); - } - - void pred_transformer::init_sig() { - if (m_sig.empty()) { - for (unsigned i = 0; i < m_head->get_arity(); ++i) { - sort * arg_sort = m_head->get_domain(i); - std::stringstream name_stm; - name_stm << m_head->get_name() << '_' << i; - func_decl_ref stm(m); - stm = m.mk_func_decl(symbol(name_stm.str().c_str()), 0, (sort*const*)nullptr, arg_sort); - m_sig.push_back(pm.get_o_pred(stm, 0)); - } - } - } - - void pred_transformer::ensure_level(unsigned level) { - if (is_infty_level(level)) { - return; - } - while (m_levels.size() <= level) { - m_solver.add_level(); - m_levels.push_back(expr_ref_vector(m)); - } - } - - bool pred_transformer::is_reachable(expr* state) { - return m_reachable.is_reachable(state); - } - - datalog::rule const& pred_transformer::find_rule(model_core const& model) const { - TRACE("pdr_verbose", - datalog::rule_manager& rm = ctx.get_context().get_rule_manager(); - for (auto const& kv : m_tag2rule) { - expr* pred = kv.m_key; - tout << mk_pp(pred, m) << ":\n"; - if (kv.m_value) rm.display_smt2(*kv.m_value, tout) << "\n"; - } - ); - - if (m_tag2rule.size() == 1) { - return *m_tag2rule.begin()->m_value; - } - - expr_ref vl(m); - for (auto const& kv : m_tag2rule) { - expr* pred = kv.m_key; - if (model.eval(to_app(pred)->get_decl(), vl) && m.is_true(vl)) { - return *kv.m_value; - } - } - throw default_exception("could not find rule"); - } - - void pred_transformer::find_predecessors(datalog::rule const& r, ptr_vector& preds) const { - preds.reset(); - unsigned tail_sz = r.get_uninterpreted_tail_size(); - for (unsigned ti = 0; ti < tail_sz; ti++) { - preds.push_back(r.get_tail(ti)->get_decl()); - } - } - - - void pred_transformer::remove_predecessors(expr_ref_vector& literals) { - // remove tags - for (unsigned i = 0; i < literals.size(); ) { - expr* l = literals[i].get(); - m.is_not(l, l); - if (m_tag2rule.contains(l)) { - literals[i] = literals.back(); - literals.pop_back(); - } - else { - ++i; - } - } - } - - void pred_transformer::simplify_formulas(tactic& tac, expr_ref_vector& v) { - goal_ref g(alloc(goal, m, false, false, false)); - for (expr* e : v) g->assert_expr(e); - goal_ref_buffer result; - tac(g, result); - SASSERT(result.size() == 1); - goal* r = result[0]; - v.reset(); - for (unsigned j = 0; j < r->size(); ++j) v.push_back(r->form(j)); - } - - void pred_transformer::simplify_formulas() { - tactic_ref us = mk_unit_subsumption_tactic(m); - simplify_formulas(*us, m_invariants); - for (auto & fmls : m_levels) - simplify_formulas(*us, fmls); - } - - expr_ref pred_transformer::get_formulas(unsigned level, bool add_axioms) { - expr_ref_vector res(m); - if (add_axioms) { - res.push_back(pm.get_background()); - res.push_back((level == 0)?initial_state():transition()); - } - res.append(m_invariants); - for (unsigned i = level; i < m_levels.size(); ++i) { - res.append(m_levels[i]); - } - return pm.mk_and(res); - } - - expr_ref pred_transformer::get_propagation_formula(decl2rel const& pts, unsigned level) { - expr_ref result(m), tmp1(m), tmp2(m); - expr_ref_vector conj(m); - if (level == 0) { - conj.push_back(initial_state()); - } - else { - conj.push_back(transition()); - } - conj.push_back(get_formulas(level, true)); - obj_map::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); - for (; level > 0 && it != end; ++it) { - expr* tag = it->m_key; - datalog::rule const* r = it->m_value; - if (!r) continue; - find_predecessors(*r, m_predicates); - for (unsigned i = 0; i < m_predicates.size(); ++i) { - func_decl* d = m_predicates[i]; - pred_transformer & pt = *pts.find(d); - tmp1 = pt.get_formulas(level-1, false); - TRACE("pdr", tout << mk_pp(tmp1, m) << "\n";); - pm.formula_n2o(tmp1, tmp2, i, false); - conj.push_back(m.mk_implies(tag, tmp2)); - } - } - return pm.mk_and(conj); - } - - bool pred_transformer::propagate_to_next_level(unsigned src_level) { - unsigned tgt_level = next_level(src_level); - ensure_level(next_level(tgt_level)); - expr_ref_vector& src = m_levels[src_level]; - - CTRACE("pdr", !src.empty(), - tout << "propagating " << src_level << " to " << tgt_level; - tout << " for relation " << head()->get_name() << "\n";); - - for (unsigned i = 0; i < src.size(); ) { - expr * curr = src[i].get(); - unsigned stored_lvl = 0; - VERIFY(m_prop2level.find(curr, stored_lvl)); - SASSERT(stored_lvl >= src_level); - bool assumes_level; - if (stored_lvl > src_level) { - TRACE("pdr", tout << "at level: "<< stored_lvl << " " << mk_pp(curr, m) << "\n";); - src[i] = src.back(); - src.pop_back(); - } - else if (is_invariant(tgt_level, curr, false, assumes_level)) { - - add_property(curr, assumes_level?tgt_level:infty_level); - TRACE("pdr", tout << "is invariant: "<< pp_level(tgt_level) << " " << mk_pp(curr, m) << "\n";); - src[i] = src.back(); - src.pop_back(); - ++m_stats.m_num_propagations; - } - else { - TRACE("pdr", tout << "not propagated: " << mk_pp(curr, m) << "\n";); - ++i; - } - } - IF_VERBOSE(3, verbose_stream() << "propagate: " << pp_level(src_level) << "\n"; - for (unsigned i = 0; i < src.size(); ++i) { - verbose_stream() << mk_pp(src[i].get(), m) << "\n"; - }); - return src.empty(); - } - - bool pred_transformer::add_property1(expr * lemma, unsigned lvl) { - if (is_infty_level(lvl)) { - if (!m_invariants.contains(lemma)) { - TRACE("pdr", tout << "property1: " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); - m_invariants.push_back(lemma); - m_prop2level.insert(lemma, lvl); - m_solver.add_formula(lemma); - return true; - } - else { - TRACE("pdr", tout << "already contained: " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); - return false; - } - } - ensure_level(lvl); - unsigned old_level; - if (!m_prop2level.find(lemma, old_level) || old_level < lvl) { - TRACE("pdr", tout << "property1: " << pp_level(lvl) << " " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); - m_levels[lvl].push_back(lemma); - m_prop2level.insert(lemma, lvl); - m_solver.add_level_formula(lemma, lvl); - return true; - } - else { - TRACE("pdr", tout << "old-level: " << pp_level(old_level) << " " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); - return false; - } - } - - void pred_transformer::add_child_property(pred_transformer& child, expr* lemma, unsigned lvl) { - ensure_level(lvl); - expr_ref_vector fmls(m); - mk_assumptions(child.head(), lemma, fmls); - for (unsigned i = 0; i < fmls.size(); ++i) { - TRACE("pdr", tout << "child property: " << mk_pp(fmls[i].get(), m) << "\n";); - if (is_infty_level(lvl)) { - m_solver.add_formula(fmls[i].get()); - } - else { - m_solver.add_level_formula(fmls[i].get(), lvl); - } - } - } - - void pred_transformer::add_property(expr* lemma, unsigned lvl) { - expr_ref_vector lemmas(m); - flatten_and(lemma, lemmas); - for (unsigned i = 0; i < lemmas.size(); ++i) { - expr* lemma_i = lemmas[i].get(); - if (add_property1(lemma_i, lvl)) { - IF_VERBOSE(2, verbose_stream() << pp_level(lvl) << " " << mk_pp(lemma_i, m) << "\n";); - for (unsigned j = 0; j < m_use.size(); ++j) { - m_use[j]->add_child_property(*this, lemma_i, next_level(lvl)); - } - } - } - } - - expr_ref pred_transformer::get_cover_delta(func_decl* p_orig, int level) { - expr_ref result(m.mk_true(), m), v(m), c(m); - if (level == -1) { - result = pm.mk_and(m_invariants); - } - else if ((unsigned)level < m_levels.size()) { - result = pm.mk_and(m_levels[level]); - } - // replace local constants by bound variables. - expr_substitution sub(m); - for (unsigned i = 0; i < sig_size(); ++i) { - c = m.mk_const(pm.o2n(sig(i), 0)); - v = m.mk_var(i, sig(i)->get_range()); - sub.insert(c, v); - } - scoped_ptr rep = mk_default_expr_replacer(m); - rep->set_substitution(&sub); - (*rep)(result); - - // adjust result according to model converter. - unsigned arity = m_head->get_arity(); - model_ref md = alloc(model, m); - if (arity == 0) { - md->register_decl(m_head, result); - } - else { - func_interp* fi = alloc(func_interp, m, arity); - fi->set_else(result); - md->register_decl(m_head, fi); - } - model_converter_ref mc = ctx.get_model_converter(); - apply(mc, md); - if (p_orig->get_arity() == 0) { - result = md->get_const_interp(p_orig); - } - else { - result = md->get_func_interp(p_orig)->get_interp(); - } - return result; - } - - void pred_transformer::add_cover(unsigned level, expr* property) { - // replace bound variables by local constants. - expr_ref result(property, m), v(m), c(m); - expr_substitution sub(m); - for (unsigned i = 0; i < sig_size(); ++i) { - c = m.mk_const(pm.o2n(sig(i), 0)); - v = m.mk_var(i, sig(i)->get_range()); - sub.insert(v, c); - } - scoped_ptr rep = mk_default_expr_replacer(m); - rep->set_substitution(&sub); - (*rep)(result); - TRACE("pdr", tout << "cover:\n" << mk_pp(result, m) << "\n";); - // add the property. - add_property(result, level); - } - - void pred_transformer::propagate_to_infinity(unsigned invariant_level) { - expr_ref inv = get_formulas(invariant_level, false); - add_property(inv, infty_level); - // cleanup - for (unsigned i = invariant_level; i < m_levels.size(); ++i) { - m_levels[i].reset(); - } - } - - lbool pred_transformer::is_reachable(model_node& n, expr_ref_vector* core, bool& uses_level) { - TRACE("pdr", - tout << "is-reachable: " << head()->get_name() << " level: " << n.level() << "\n"; - tout << mk_pp(n.state(), m) << "\n";); - ensure_level(n.level()); - model_ref model; - prop_solver::scoped_level _sl(m_solver, n.level()); - m_solver.set_core(core); - m_solver.set_model(&model); - lbool is_sat = m_solver.check_conjunction_as_assumptions(n.state()); - if (is_sat == l_true && core) { - core->reset(); - TRACE("pdr", tout << "updating model\n"; - model_smt2_pp(tout, m, *model, 0); - tout << mk_pp(n.state(), m) << "\n";); - n.set_model(model); - } - else if (is_sat == l_false) { - uses_level = m_solver.assumes_level(); - } - m_solver.set_model(nullptr); - return is_sat; - } - - bool pred_transformer::is_invariant(unsigned level, expr* states, bool inductive, bool& assumes_level, expr_ref_vector* core) { - expr_ref_vector conj(m); - expr_ref tmp(m); - - conj.push_back(m.mk_not(states)); - - if (inductive) { - mk_assumptions(head(), states, conj); - } - tmp = pm.mk_and(conj); - prop_solver::scoped_level _sl(m_solver, level); - m_solver.set_core(core); - m_solver.set_model(nullptr); - lbool r = m_solver.check_conjunction_as_assumptions(tmp); - if (r == l_false) { - assumes_level = m_solver.assumes_level(); - } - return r == l_false; - } - - bool pred_transformer::check_inductive(unsigned level, expr_ref_vector& lits, bool& assumes_level) { - manager& pm = get_pdr_manager(); - expr_ref_vector conj(m), core(m); - expr_ref fml(m), states(m); - states = m.mk_not(pm.mk_and(lits)); - mk_assumptions(head(), states, conj); - fml = pm.mk_and(conj); - prop_solver::scoped_level _sl(m_solver, level); - m_solver.set_core(&core); - m_solver.set_subset_based_core(true); - m_solver.set_model(nullptr); - lbool res = m_solver.check_assumptions_and_formula(lits, fml); - if (res == l_false) { - lits.reset(); - lits.append(core); - assumes_level = m_solver.assumes_level(); - } - return res == l_false; - } - - void pred_transformer::mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result) { - expr_ref tmp1(m), tmp2(m); - obj_map::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); - for (; it != end; ++it) { - expr* pred = it->m_key; - datalog::rule const* r = it->m_value; - if (!r) continue; - find_predecessors(*r, m_predicates); - for (unsigned i = 0; i < m_predicates.size(); i++) { - func_decl* d = m_predicates[i]; - if (d == head) { - tmp1 = m.mk_implies(pred, fml); - pm.formula_n2o(tmp1, tmp2, i); - result.push_back(tmp2); - } - } - } - } - - void pred_transformer::initialize(decl2rel const& pts) { - m_initial_state = m.mk_false(); - m_transition = m.mk_true(); - init_rules(pts, m_initial_state, m_transition); - th_rewriter rw(m); - rw(m_transition); - rw(m_initial_state); - - m_solver.add_formula(m_transition); - m_solver.add_level_formula(m_initial_state, 0); - TRACE("pdr", - tout << "Initial state: " << mk_pp(m_initial_state, m) << "\n"; - tout << "Transition: " << mk_pp(m_transition, m) << "\n";); - SASSERT(is_app(m_initial_state)); - m_reachable.add_init(to_app(m_initial_state)); - } - - void pred_transformer::init_rules(decl2rel const& pts, expr_ref& init, expr_ref& transition) { - expr_ref_vector transitions(m); - ptr_vector tr_rules; - datalog::rule const* rule; - expr_ref_vector disj(m); - app_ref pred(m); - for (unsigned i = 0; i < rules().size(); ++i) { - init_rule(pts, *rules()[i], init, tr_rules, transitions); - } - switch(transitions.size()) { - case 0: - transition = m.mk_false(); - break; - case 1: - // create a dummy tag. - pred = m.mk_fresh_const(head()->get_name().str().c_str(), m.mk_bool_sort()); - rule = tr_rules[0]; - m_tag2rule.insert(pred, rule); - m_rule2tag.insert(rule, pred.get()); - transitions.push_back(pred); - transition = pm.mk_and(transitions); - break; - default: - for (unsigned i = 0; i < transitions.size(); ++i) { - pred = m.mk_fresh_const(head()->get_name().str().c_str(), m.mk_bool_sort()); - rule = tr_rules[i]; - m_tag2rule.insert(pred, rule); - m_rule2tag.insert(rule, pred); - disj.push_back(pred); - transitions[i] = m.mk_implies(pred, transitions[i].get()); - } - transitions.push_back(m.mk_or(disj.size(), disj.c_ptr())); - transition = pm.mk_and(transitions); - break; - } - } - - void pred_transformer::init_rule( - decl2rel const& pts, - datalog::rule const& rule, - expr_ref& init, - ptr_vector& rules, - expr_ref_vector& transitions) - { - // Predicates that are variable representatives. Other predicates at - // positions the variables occur are made equivalent with these. - expr_ref_vector conj(m); - app_ref_vector var_reprs(m); - ptr_vector aux_vars; - - unsigned ut_size = rule.get_uninterpreted_tail_size(); - unsigned t_size = rule.get_tail_size(); - SASSERT(ut_size <= t_size); - init_atom(pts, rule.get_head(), var_reprs, conj, UINT_MAX); - for (unsigned i = 0; i < ut_size; ++i) { - if (rule.is_neg_tail(i)) { - char const* msg = "PDR does not supported negated predicates in rule tails"; - IF_VERBOSE(0, verbose_stream() << msg << "\n";); - throw default_exception(msg); - } - init_atom(pts, rule.get_tail(i), var_reprs, conj, i); - } - for (unsigned i = ut_size; i < t_size; ++i) { - ground_free_vars(rule.get_tail(i), var_reprs, aux_vars); - } - SASSERT(check_filled(var_reprs)); - expr_ref_vector tail(m); - for (unsigned i = ut_size; i < t_size; ++i) { - tail.push_back(rule.get_tail(i)); - } - flatten_and(tail); - for (unsigned i = 0; i < tail.size(); ++i) { - expr_ref tmp(m); - var_subst vs(m, false); - vs(tail[i].get(), var_reprs.size(), (expr*const*)var_reprs.c_ptr(), tmp); - conj.push_back(tmp); - TRACE("pdr", tout << mk_pp(tail[i].get(), m) << "\n" << mk_pp(tmp, m) << "\n";); - if (!is_ground(tmp)) { - std::stringstream msg; - msg << "PDR cannot solve non-ground tails: " << tmp; - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - } - expr_ref fml = pm.mk_and(conj); - th_rewriter rw(m); - rw(fml); - if (ctx.is_dl() || ctx.is_utvpi()) { - blast_term_ite(fml); - } - TRACE("pdr", tout << mk_pp(fml, m) << "\n";); - SASSERT(is_ground(fml)); - if (m.is_false(fml)) { - // no-op. - } - else { - if (ut_size == 0) { - init = m.mk_or(init, fml); - } - transitions.push_back(fml); - m.inc_ref(fml); - m_rule2transition.insert(&rule, fml.get()); - rules.push_back(&rule); - } - m_rule2inst.insert(&rule, alloc(app_ref_vector, var_reprs)); - m_rule2vars.insert(&rule, aux_vars); - TRACE("pdr", - tout << rule.get_decl()->get_name() << "\n"; - for (unsigned i = 0; i < var_reprs.size(); ++i) { - tout << mk_pp(var_reprs[i].get(), m) << " "; - } - if (!var_reprs.empty()) tout << "\n";); - } - - bool pred_transformer::check_filled(app_ref_vector const& v) const { - for (unsigned i = 0; i < v.size(); ++i) { - if (!v[i]) return false; - } - return true; - } - - // create constants for free variables in tail. - void pred_transformer::ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector& aux_vars) { - expr_free_vars fv; - fv(e); - while (vars.size() < fv.size()) { - vars.push_back(nullptr); - } - for (unsigned i = 0; i < fv.size(); ++i) { - if (fv[i] && !vars[i].get()) { - vars[i] = m.mk_fresh_const("aux", fv[i]); - aux_vars.push_back(vars[i].get()); - } - } - } - - // create names for variables used in relations. - void pred_transformer::init_atom( - decl2rel const& pts, - app * atom, - app_ref_vector& var_reprs, - expr_ref_vector& conj, - unsigned tail_idx - ) - { - unsigned arity = atom->get_num_args(); - func_decl* head = atom->get_decl(); - pred_transformer& pt = *pts.find(head); - for (unsigned i = 0; i < arity; i++) { - app_ref rep(m); - - if (tail_idx == UINT_MAX) { - rep = m.mk_const(pm.o2n(pt.sig(i), 0)); - } - else { - rep = m.mk_const(pm.o2o(pt.sig(i), 0, tail_idx)); - } - - expr * arg = atom->get_arg(i); - if (is_var(arg)) { - var * v = to_var(arg); - unsigned var_idx = v->get_idx(); - if (var_idx >= var_reprs.size()) { - var_reprs.resize(var_idx+1); - } - expr * repr = var_reprs[var_idx].get(); - if (repr) { - conj.push_back(m.mk_eq(rep, repr)); - } - else { - var_reprs[var_idx] = rep; - } - } - else { - SASSERT(is_app(arg)); - conj.push_back(m.mk_eq(rep, arg)); - } - } - } - - void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r) { - r.push_back(pm.get_background()); - r.push_back((lvl == 0)?initial_state():transition()); - for (unsigned i = 0; i < rules().size(); ++i) { - add_premises(pts, lvl, *rules()[i], r); - } - } - - void pred_transformer::close(expr* e) { - m_reachable.add_reachable(e); - } - - void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r) { - find_predecessors(rule, m_predicates); - for (unsigned i = 0; i < m_predicates.size(); ++i) { - expr_ref tmp(m); - func_decl* head = m_predicates[i]; - pred_transformer& pt = *pts.find(head); - expr_ref inv = pt.get_formulas(lvl, false); - if (!m.is_true(inv)) { - pm.formula_n2o(inv, tmp, i, true); - r.push_back(tmp); - } - } - } - - void pred_transformer::inherit_properties(pred_transformer& other) { - SASSERT(m_head == other.m_head); - obj_map::iterator it = other.m_prop2level.begin(); - obj_map::iterator end = other.m_prop2level.end(); - for (; it != end; ++it) { - IF_VERBOSE(2, verbose_stream() << "(pdr-inherit: " << mk_pp(it->m_key, m) << ")\n";); - add_property(it->m_key, it->m_value); - } - } - - // ---------------- - // model_node - - void model_node::set_closed() { - TRACE("pdr", tout << state() << "\n";); - pt().close(state()); - m_closed = true; - } - - void model_node::set_open() { - SASSERT(m_closed); - m_closed = false; - model_node* p = parent(); - while (p && p->is_closed()) { - p->m_closed = false; - p = p->parent(); - } - } - - void model_node::check_pre_closed() { - for (unsigned i = 0; i < children().size(); ++i) { - if (children()[i]->is_open()) return; - } - set_pre_closed(); - model_node* p = parent(); - while (p && p->is_1closed()) { - p->set_pre_closed(); - p = p->parent(); - } - } - - static bool is_ini(datalog::rule const& r) { - return r.get_uninterpreted_tail_size() == 0; - } - - datalog::rule* model_node::get_rule() { - if (m_rule) { - return const_cast(m_rule); - } - // only initial states are not set by the PDR search. - SASSERT(m_model.get()); - if (!m_model.get()) { - std::stringstream msg; - msg << "no model for node " << state(); - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - - datalog::rule const& rl1 = pt().find_rule(*m_model); - if (is_ini(rl1)) { - set_rule(&rl1); - return const_cast(m_rule); - } - ast_manager& m = pt().get_manager(); - // otherwise, the initial state is reachable. - ptr_vector const& rules = pt().rules(); - ptr_vector ini_rules; - expr_ref_vector tags(m); - expr_ref ini_tags(m), ini_state(m); - for (unsigned i = 0; i < rules.size(); ++i) { - datalog::rule* rl = rules[i]; - if (is_ini(*rl)) { - tags.push_back(pt().rule2tag(rl)); - } - } - SASSERT(!tags.empty()); - ini_tags = ::mk_or(tags); - ini_state = m.mk_and(ini_tags, pt().initial_state(), state()); - model_ref mdl; - pt().get_solver().set_model(&mdl); - TRACE("pdr", tout << ini_state << "\n";); - if (l_true != pt().get_solver().check_conjunction_as_assumptions(ini_state)) { - std::stringstream msg; - msg << "Unsatisfiable initial state: " << ini_state << "\n"; - display(msg, 2); - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - SASSERT(mdl.get()); - datalog::rule const& rl2 = pt().find_rule(*mdl); - SASSERT(is_ini(rl2)); - set_rule(&rl2); - pt().get_solver().set_model(nullptr); - return const_cast(m_rule); - } - - - void model_node::mk_instantiate(datalog::rule_ref& r0, datalog::rule_ref& r1, expr_ref_vector& binding) { - ast_manager& m = pt().get_manager(); - expr_ref_vector conjs(m); - obj_map model; - flatten_and(state(), conjs); - for (unsigned i = 0; i < conjs.size(); ++i) { - expr* e = conjs[i].get(), *e1, *e2; - if (m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) { - if (m.is_value(e2)) { - model.insert(e1, e2); - } - else if (m.is_value(e1)) { - model.insert(e2, e1); - } - } - else if (m.is_not(e, e1)) { - model.insert(e1, m.mk_false()); - } - else { - model.insert(e, m.mk_true()); - } - } - r0 = get_rule(); - app_ref_vector& inst = pt().get_inst(r0); - TRACE("pdr", tout << state() << " instance: " << inst.size() << "\n";); - for (unsigned i = 0; i < inst.size(); ++i) { - expr* v; - if (model.find(inst[i].get(), v)) { - binding.push_back(v); - } - else { - binding.push_back(m.mk_var(i, m.get_sort(inst[i].get()))); - } - } - r1 = r0; - if (!inst.empty()) { - r1.get_manager().substitute(r1, binding.size(), binding.c_ptr()); - } - } - - - - std::ostream& model_node::display(std::ostream& out, unsigned indent) { - for (unsigned i = 0; i < indent; ++i) out << " "; - out << m_level << " " << m_pt.head()->get_name() << " " << (m_closed?"closed":"open") << "\n"; - for (unsigned i = 0; i < indent; ++i) out << " "; - out << " " << mk_pp(m_state, m_state.get_manager(), indent) << " " << m_state->get_id() << "\n"; - for (unsigned i = 0; i < children().size(); ++i) { - children()[i]->display(out, indent + 1); - } - return out; - } - - unsigned model_node::index() const { - model_node* p = parent(); - if (!p) return 0; - for (unsigned i = 0; i < p->children().size(); ++i) { - if (this == p->children()[i]) return i; - } - UNREACHABLE(); - return 0; - } - - - void model_node::dequeue(model_node*& root) { - TRACE("pdr", tout << this << " root: " << root << " " << state() << "\n";); - if (!m_next && !m_prev) return; - SASSERT(m_next); - SASSERT(m_prev); - SASSERT(children().empty()); - if (this == m_next) { - SASSERT(m_prev == this); - if (root == this) { - root = nullptr; - } - } - else { - m_next->m_prev = m_prev; - m_prev->m_next = m_next; - if (this == root) { - root = m_next; - } - } - TRACE("pdr", tout << "new root: " << root << "\n";); - m_prev = nullptr; - m_next = nullptr; - } - - - void model_node::enqueue(model_node* n) { - TRACE("pdr", tout << n << " " << n->state() << "\n";); - SASSERT(!n->m_next); - SASSERT(!n->m_prev); - if (this == n) { - m_next = n; - m_prev = n; - } - else { - n->m_next = m_next; - m_next->m_prev = n; - m_next = n; - n->m_prev = this; - } - } - // ---------------- - // model_search - - /** - \brief Dequeue the next goal. - */ - model_node* model_search::next() { - if (!m_goal) { - return nullptr; - } - else { - model_node* result = m_goal; - result->dequeue(m_goal); - return result; - } - } - - - void model_search::add_leaf(model_node& n) { - SASSERT(n.children().empty()); - model_nodes ns; - model_nodes& nodes = cache(n).insert_if_not_there2(n.state(), ns)->get_data().m_value; - if (nodes.contains(&n)) { - return; - } - nodes.push_back(&n); - TRACE("pdr_verbose", tout << "add: " << n.level() << ": " << &n << " " << n.state() << "\n";); - if (nodes.size() == 1) { - set_leaf(n); - } - else { - n.set_pre_closed(); - } - } - - void model_search::set_leaf(model_node& n) { - erase_children(n, true); - SASSERT(n.is_open()); - enqueue_leaf(&n); - } - - void model_search::enqueue_leaf(model_node* n) { - TRACE("pdr_verbose", tout << "node: " << n << " " << n->state() << " goal: " << m_goal << "\n";); - SASSERT(n->is_open()); - if (!m_goal) { - m_goal = n; - m_goal->enqueue(n); - } - else if (m_bfs) { - m_goal->enqueue(n); - } - else { - m_goal->next()->enqueue(n); - } - } - - void model_search::set_root(model_node* root) { - reset(); - m_root = root; - SASSERT(cache(*root).empty()); - cache(*root).insert(root->state(), 1); - set_leaf(*root); - } - - obj_map >& model_search::cache(model_node const& n) { - unsigned l = n.orig_level(); - if (l >= m_cache.size()) { - m_cache.resize(l + 1); - } - return m_cache[l]; - } - - void model_search::erase_children(model_node& n, bool backtrack) { - ptr_vector todo, nodes; - todo.append(n.children()); - remove_goal(n); - n.reset(); - while (!todo.empty()) { - model_node* m = todo.back(); - todo.pop_back(); - nodes.push_back(m); - todo.append(m->children()); - remove_node(*m, backtrack); - } - std::for_each(nodes.begin(), nodes.end(), delete_proc()); - } - - void model_search::remove_node(model_node& n, bool backtrack) { - TRACE("pdr_verbose", tout << "remove: " << n.level() << ": " << &n << " " << n.state() << "\n";); - model_nodes& nodes = cache(n).find(n.state()); - nodes.erase(&n); - remove_goal(n); - // TBD: siblings would also fail if n is not a goal. - if (!nodes.empty() && backtrack && nodes[0]->children().empty() && nodes[0]->is_closed()) { - TRACE("pdr_verbose", for (unsigned i = 0; i < nodes.size(); ++i) n.display(tout << &n << "\n", 2);); - model_node* n1 = nodes[0]; - n1->set_open(); - enqueue_leaf(n1); - } - - if (!nodes.empty() && n.get_model_ptr() && backtrack) { - model_ref mr(n.get_model_ptr()); - nodes[0]->set_model(mr); - } - if (nodes.empty()) { - cache(n).remove(n.state()); - } - } - - void model_search::remove_goal(model_node& n) { - n.dequeue(m_goal); - } - - void model_search::well_formed() { - // each open leaf is in the set of m_goal. - ptr_vector nodes; - nodes.push_back(&get_root()); - for (unsigned i = 0; i < nodes.size(); ++i) { - model_node* n = nodes[i]; - if (!n->children().empty()) { - nodes.append(n->children()); - } - else if (n->is_open() && !n->is_goal() && n->parent()) { - TRACE("pdr", n->display(tout << "node " << n << " not found among leaves\n", 0); display(tout);); - UNREACHABLE(); - return; - } - } - if (m_goal) { - model_node* n = m_goal; - do { - if (!n->is_open() || !n->children().empty()) { - TRACE("pdr", n->display(tout << "invalid leaf\n", 0); - display(tout);); - UNREACHABLE(); - return; - } - n = n->next(); - } - while (m_goal != n); - } - - // each state appears in at most one goal per level. - bool found = true; - for (unsigned l = 0; m_goal && found; ++l) { - found = false; - obj_hashtable open_states; - model_node* n = m_goal; - do { - if (n->level() == l) { - found = true; - if (n->is_open()) { - if (open_states.contains(n->state())) { - TRACE("pdr", n->display(tout << "repeated leaf\n", 0); display(tout);); - UNREACHABLE(); - } - open_states.insert(n->state()); - } - } - n = n->next(); - } - while (m_goal != n); - } - // a node is open if and only if it contains an - // open child which is a goal. - for (unsigned i = 0; i < nodes.size(); ++i) { - model_node* n = nodes[i]; - if (!n->children().empty() && n->parent()) { - found = false; - for (unsigned j = 0; !found && j < n->children().size(); ++j) { - found = n->children()[j]->is_open(); - } - if (n->is_open() != found) { - TRACE("pdr", n->display(tout << "node in inconsistent state\n", 0); display(tout);); - UNREACHABLE(); - } - } - } - } - - unsigned model_search::num_goals() const { - model_node* n = m_goal; - unsigned num = 0; - if (n) { - do { - ++num; - n = n->next(); - } - while (n != m_goal); - } - return num; - } - - std::ostream& model_search::display(std::ostream& out) const { - if (m_root) { - m_root->display(out, 0); - } - out << "goals " << num_goals() << "\n"; - model_node* n = m_goal; - if (n) { - do { - n->display(out, 1); - n = n->next(); - } - while (n != m_goal); - } - return out; - } - - /** - \brief Ensure that all nodes in the tree have associated models. - get_trace and get_proof_trace rely on models to extract rules. - */ - void model_search::update_models() { - obj_map models; - obj_map rules; - ptr_vector todo; - todo.push_back(m_root); - while (!todo.empty()) { - model_node* n = todo.back(); - if (n->get_model_ptr()) { - models.insert(n->state(), n->get_model_ptr()); - rules.insert(n->state(), n->get_rule()); - } - todo.pop_back(); - todo.append(n->children().size(), n->children().c_ptr()); - } - - todo.push_back(m_root); - while (!todo.empty()) { - model_node* n = todo.back(); - model* md = nullptr; - if (!n->get_model_ptr()) { - if (models.find(n->state(), md)) { - TRACE("pdr", tout << n->state() << "\n";); - model_ref mr(md); - n->set_model(mr); - datalog::rule const* rule = rules.find(n->state()); - n->set_rule(rule); - } - else { - TRACE("pdr", tout << "no model for " << n->state() << "\n";); - IF_VERBOSE(1, n->display(verbose_stream() << "no model:\n", 0); - verbose_stream() << n->state() << "\n";); - } - } - else { - TRACE("pdr", tout << n->state() << "\n";); - } - todo.pop_back(); - todo.append(n->children().size(), n->children().c_ptr()); - } - } - - /** - Extract trace comprising of constraints - and predicates that are satisfied from facts to the query. - The resulting trace - */ - expr_ref model_search::get_trace(context const& ctx) { - pred_transformer& pt = get_root().pt(); - ast_manager& m = pt.get_manager(); - manager& pm = pt.get_pdr_manager(); - datalog::context& dctx = ctx.get_context(); - datalog::rule_manager& rm = dctx.get_rule_manager(); - expr_ref_vector constraints(m), predicates(m); - expr_ref tmp(m); - ptr_vector children; - unsigned deltas[2]; - datalog::rule_ref rule(rm), r0(rm); - model_node* n = m_root; - datalog::rule_counter& vc = rm.get_counter(); - substitution subst(m); - unifier unif(m); - rule = n->get_rule(); - unsigned max_var = vc.get_max_rule_var(*rule); - predicates.push_back(rule->get_head()); - children.push_back(n); - bool first = true; - update_models(); - while (!children.empty()) { - SASSERT(children.size() == predicates.size()); - expr_ref_vector binding(m); - n = children.back(); - children.pop_back(); - TRACE("pdr", n->display(tout, 0);); - n->mk_instantiate(r0, rule, binding); - - max_var = std::max(max_var, vc.get_max_rule_var(*rule)); - subst.reset(); - subst.reserve(2, max_var+1); - deltas[0] = 0; - deltas[1] = max_var+1; - - VERIFY(unif(predicates.back(), rule->get_head(), subst)); - for (unsigned i = 0; i < constraints.size(); ++i) { - subst.apply(2, deltas, expr_offset(constraints[i].get(), 0), tmp); - dctx.get_rewriter()(tmp); - constraints[i] = tmp; - } - for (unsigned i = 0; i < predicates.size(); ++i) { - subst.apply(2, deltas, expr_offset(predicates[i].get(), 0), tmp); - predicates[i] = tmp; - } - if (!first) { - constraints.push_back(predicates.back()); - } - first = false; - predicates.pop_back(); - for (unsigned i = rule->get_uninterpreted_tail_size(); i < rule->get_tail_size(); ++i) { - subst.apply(2, deltas, expr_offset(rule->get_tail(i), 1), tmp); - constraints.push_back(tmp); - } - for (unsigned i = 0; i < constraints.size(); ++i) { - max_var = std::max(vc.get_max_var(constraints[i].get()), max_var); - } - if (n->children().empty()) { - // nodes whose states are repeated - // in the search tree do not have children. - continue; - } - - SASSERT(n->children().size() == rule->get_uninterpreted_tail_size()); - - for (unsigned i = 0; i < rule->get_uninterpreted_tail_size(); ++i) { - subst.apply(2, deltas, expr_offset(rule->get_tail(i), 1), tmp); - predicates.push_back(tmp); - } - for (unsigned i = 0; i < predicates.size(); ++i) { - max_var = std::max(vc.get_max_var(predicates[i].get()), max_var); - } - - children.append(n->children()); - } - expr_safe_replace repl(m); - for (unsigned i = 0; i < constraints.size(); ++i) { - expr* e = constraints[i].get(), *e1, *e2; - if (m.is_eq(e, e1, e2) && is_var(e1) && is_ground(e2)) { - repl.insert(e1, e2); - } - else if (m.is_eq(e, e1, e2) && is_var(e2) && is_ground(e1)) { - repl.insert(e2, e1); - } - } - expr_ref_vector result(m); - for (unsigned i = 0; i < constraints.size(); ++i) { - expr_ref tmp(m); - tmp = constraints[i].get(); - repl(tmp); - dctx.get_rewriter()(tmp); - if (!m.is_true(tmp)) { - result.push_back(tmp); - } - } - return pm.mk_and(result); - } - - proof_ref model_search::get_proof_trace(context const& ctx) { - pred_transformer& pt = get_root().pt(); - ast_manager& m = pt.get_manager(); - datalog::context& dctx = ctx.get_context(); - datalog::rule_manager& rm = dctx.get_rule_manager(); - datalog::rule_unifier unif(dctx); - datalog::dl_decl_util util(m); - datalog::rule_ref r0(rm), r1(rm); - obj_map cache; - obj_map rules; - ptr_vector todo; - proof_ref_vector trail(m); - datalog::rule_ref_vector rules_trail(rm); - proof* pr = nullptr; - unif.set_normalize(true); - todo.push_back(m_root); - update_models(); - while (!todo.empty()) { - model_node* n = todo.back(); - TRACE("pdr", n->display(tout, 0);); - if (cache.find(n->state(), pr)) { - todo.pop_back(); - continue; - } - ptr_vector pfs; - ptr_vector rls; - ptr_vector const& chs = n->children(); - pfs.push_back(0); - rls.push_back(0); - for (unsigned i = 0; i < chs.size(); ++i) { - if (cache.find(chs[i]->state(), pr)) { - pfs.push_back(pr); - rls.push_back(rules.find(chs[i]->state())); - } - else { - todo.push_back(chs[i]); - } - } - if (pfs.size() != 1 + chs.size()) { - continue; - } - proof_ref rl(m); - expr_ref_vector binding(m); - n->mk_instantiate(r0, r1, binding); - proof_ref p1(m), p2(m); - p1 = r0->get_proof(); - IF_VERBOSE(0, if (!p1) r0->display(dctx, verbose_stream());); - SASSERT(p1); - pfs[0] = p1; - rls[0] = r1; - TRACE("pdr", - tout << "root: " << mk_pp(p1.get(), m) << "\n"; - for (unsigned i = 0; i < binding.size(); ++i) { - tout << mk_pp(binding[i].get(), m) << "\n"; - } - for (unsigned i = 1; i < pfs.size(); ++i) { - tout << mk_pp(pfs[i], m) << "\n"; - } - ); - datalog::rule_ref reduced_rule(rm), r3(rm); - reduced_rule = rls[0]; - // check if binding is identity. - bool binding_is_id = true; - for (unsigned i = 0; binding_is_id && i < binding.size(); ++i) { - expr* v = binding[i].get(); - binding_is_id = is_var(v) && to_var(v)->get_idx() == i; - } - if (rls.size() > 1 || !binding_is_id) { - expr_ref tmp(m); - vector substs; - svector > positions; - substs.push_back(binding); // TODO base substitution. - for (unsigned i = 1; i < rls.size(); ++i) { - datalog::rule& src = *rls[i]; - bool unified = unif.unify_rules(*reduced_rule, 0, src); - if (!unified) { - IF_VERBOSE(0, - verbose_stream() << "Could not unify rules: "; - reduced_rule->display(dctx, verbose_stream()); - src.display(dctx, verbose_stream());); - } - expr_ref_vector sub1 = unif.get_rule_subst(*reduced_rule, true); - TRACE("pdr", - for (unsigned k = 0; k < sub1.size(); ++k) { - tout << mk_pp(sub1[k].get(), m) << " "; - } - tout << "\n"; - ); - - for (unsigned j = 0; j < substs.size(); ++j) { - for (unsigned k = 0; k < substs[j].size(); ++k) { - var_subst(m, false)(substs[j][k].get(), sub1.size(), sub1.c_ptr(), tmp); - substs[j][k] = tmp; - } - while (substs[j].size() < sub1.size()) { - substs[j].push_back(sub1[substs[j].size()].get()); - } - } - - positions.push_back(std::make_pair(i,0)); - substs.push_back(unif.get_rule_subst(src, false)); - VERIFY(unif.apply(*reduced_rule.get(), 0, src, r3)); - reduced_rule = r3; - } - - expr_ref fml_concl(m); - rm.to_formula(*reduced_rule.get(), fml_concl); - p1 = m.mk_hyper_resolve(pfs.size(), pfs.c_ptr(), fml_concl, positions, substs); - - } - cache.insert(n->state(), p1); - rules.insert(n->state(), reduced_rule); - trail.push_back(p1); - rules_trail.push_back(reduced_rule); - todo.pop_back(); - } - return proof_ref(cache.find(m_root->state()), m); - } - - model_search::~model_search() { - TRACE("pdr", tout << "\n";); - reset(); - } - - void model_search::reset() { - if (m_root) { - erase_children(*m_root, false); - remove_node(*m_root, false); - dealloc(m_root); - m_root = nullptr; - } - m_cache.reset(); - } - - void model_search::backtrack_level(bool uses_level, model_node& n) { - SASSERT(m_root); - if (uses_level && m_root->level() > n.level()) { - IF_VERBOSE(2, verbose_stream() << "Increase level " << n.level() << "\n";); - n.increase_level(); - enqueue_leaf(&n); - } - else { - model_node* p = n.parent(); - if (p) { - set_leaf(*p); - } - } - DEBUG_CODE(well_formed();); - } - - // ---------------- - // context - - context::context( - smt_params& fparams, - fixedpoint_params const& params, - ast_manager& m - ) - : m_fparams(fparams), - m_params(params), - m(m), - m_context(nullptr), - m_pm(m_fparams, params.pdr_max_num_contexts(), m), - m_query_pred(m), - m_query(nullptr), - m_search(m_params.pdr_bfs_model_search()), - m_last_result(l_undef), - m_inductive_lvl(0), - m_expanded_lvl(0) - { - } - - context::~context() { - reset_core_generalizers(); - reset(); - } - - void context::reset(decl2rel& rels) { - decl2rel::iterator it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - dealloc(it->m_value); - } - rels.reset(); - } - - void context::reset(bool full) { - TRACE("pdr", tout << "reset\n";); - reset(m_rels); - if (full) { - reset(m_rels_tmp); - } - m_search.reset(); - m_query = nullptr; - m_last_result = l_undef; - m_inductive_lvl = 0; - } - - void context::init_rules(datalog::rule_set& rules, decl2rel& rels) { - m_context = &rules.get_context(); - // Allocate collection of predicate transformers - datalog::rule_set::decl2rules::iterator dit = rules.begin_grouped_rules(), dend = rules.end_grouped_rules(); - decl2rel::obj_map_entry* e; - for (; dit != dend; ++dit) { - func_decl* pred = dit->m_key; - TRACE("pdr", tout << mk_pp(pred, m) << "\n";); - SASSERT(!rels.contains(pred)); - e = rels.insert_if_not_there2(pred, alloc(pred_transformer, *this, get_pdr_manager(), pred)); - datalog::rule_vector const& pred_rules = *dit->m_value; - for (unsigned i = 0; i < pred_rules.size(); ++i) { - e->get_data().m_value->add_rule(pred_rules[i]); - } - } - TRACE("pdr", tout << "adding rules\n";); - datalog::rule_set::iterator rit = rules.begin(), rend = rules.end(); - for (; rit != rend; ++rit) { - datalog::rule* r = *rit; - pred_transformer* pt; - unsigned utz = r->get_uninterpreted_tail_size(); - for (unsigned i = 0; i < utz; ++i) { - func_decl* pred = r->get_decl(i); - if (!rels.find(pred, pt)) { - pt = alloc(pred_transformer, *this, get_pdr_manager(), pred); - rels.insert(pred, pt); - } - } - } - // Initialize use list dependencies - TRACE("pdr", tout << "initialize use list dependencies\n";); - decl2rel::iterator it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - func_decl* pred = it->m_key; - pred_transformer* pt = it->m_value, *pt_user; - obj_hashtable const& deps = rules.get_dependencies().get_deps(pred); - obj_hashtable::iterator itf = deps.begin(), endf = deps.end(); - for (; itf != endf; ++itf) { - TRACE("pdr", tout << mk_pp(pred, m) << " " << mk_pp(*itf, m) << "\n";); - pt_user = rels.find(*itf); - pt_user->add_use(pt); - } - } - - TRACE("pdr", tout << "initialize predicate transformers\n";); - // Initialize the predicate transformers. - it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - SASSERT(it->m_value); - pred_transformer& rel = *it->m_value; - rel.initialize(rels); - TRACE("pdr", rel.display(tout); ); - } - } - - void context::update_rules(datalog::rule_set& rules) { - TRACE("pdr", tout << "update rules\n";); - reset(m_rels_tmp); - init_core_generalizers(rules); - init_rules(rules, m_rels_tmp); - decl2rel::iterator it = m_rels_tmp.begin(), end = m_rels_tmp.end(); - for (; it != end; ++it) { - pred_transformer* pt = nullptr; - if (m_rels.find(it->m_key, pt)) { - it->m_value->inherit_properties(*pt); - } - } - reset(false); - it = m_rels_tmp.begin(), end = m_rels_tmp.end(); - for (; it != end; ++it) { - m_rels.insert(it->m_key, it->m_value); - } - m_rels_tmp.reset(); - TRACE("pdr", tout << "done update rules\n";); - } - - unsigned context::get_num_levels(func_decl* p) { - pred_transformer* pt = nullptr; - if (m_rels.find(p, pt)) { - return pt->get_num_levels(); - } - else { - IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); - return 0; - } - } - - expr_ref context::get_cover_delta(int level, func_decl* p_orig, func_decl* p) { - pred_transformer* pt = nullptr; - if (m_rels.find(p, pt)) { - return pt->get_cover_delta(p_orig, level); - } - else { - IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); - return expr_ref(m.mk_true(), m); - } - } - - void context::add_cover(int level, func_decl* p, expr* property) { - pred_transformer* pt = nullptr; - if (!m_rels.find(p, pt)) { - pt = alloc(pred_transformer, *this, get_pdr_manager(), p); - m_rels.insert(p, pt); - IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); - } - unsigned lvl = (level == -1)?infty_level:((unsigned)level); - pt->add_cover(lvl, property); - } - - class context::classifier_proc { - ast_manager& m; - arith_util a; - bool m_is_bool; - bool m_is_bool_arith; - bool m_has_arith; - bool m_is_dl; - bool m_is_utvpi; - public: - classifier_proc(ast_manager& m, datalog::rule_set& rules): - m(m), a(m), m_is_bool(true), m_is_bool_arith(true), m_has_arith(false), m_is_dl(false), m_is_utvpi(false) { - classify(rules); - } - void operator()(expr* e) { - if (m_is_bool) { - if (!m.is_bool(e)) { - m_is_bool = false; - } - else if (is_var(e)) { - // no-op. - } - else if (!is_app(e)) { - m_is_bool = false; - } - else if (to_app(e)->get_num_args() > 0 && - to_app(e)->get_family_id() != m.get_basic_family_id()) { - m_is_bool = false; - } - } - - m_has_arith = m_has_arith || a.is_int_real(e); - - if (m_is_bool_arith) { - if (!m.is_bool(e) && !a.is_int_real(e)) { - m_is_bool_arith = false; - } - else if (is_var(e)) { - // no-op - } - else if (!is_app(e)) { - m_is_bool_arith = false; - } - else if (to_app(e)->get_num_args() > 0 && - to_app(e)->get_family_id() != m.get_basic_family_id() && - to_app(e)->get_family_id() != a.get_family_id()) { - m_is_bool_arith = false; - } - } - } - - bool is_bool() const { return m_is_bool; } - - bool is_bool_arith() const { return m_is_bool_arith; } - - bool is_dl() const { return m_is_dl; } - - bool is_utvpi() const { return m_is_utvpi; } - - private: - - void classify(datalog::rule_set& rules) { - expr_fast_mark1 mark; - datalog::rule_set::iterator it = rules.begin(), end = rules.end(); - for (; it != end; ++it) { - datalog::rule& r = *(*it); - classify_pred(mark, r.get_head()); - unsigned utsz = r.get_uninterpreted_tail_size(); - for (unsigned i = 0; i < utsz; ++i) { - classify_pred(mark, r.get_tail(i)); - } - for (unsigned i = utsz; i < r.get_tail_size(); ++i) { - quick_for_each_expr(*this, mark, r.get_tail(i)); - } - } - mark.reset(); - - m_is_dl = false; - m_is_utvpi = false; - if (m_has_arith) { - ptr_vector forms; - for (it = rules.begin(); it != end; ++it) { - datalog::rule& r = *(*it); - unsigned utsz = r.get_uninterpreted_tail_size(); - forms.push_back(r.get_head()); - for (unsigned i = utsz; i < r.get_tail_size(); ++i) { - forms.push_back(r.get_tail(i)); - } - } - m_is_dl = is_difference_logic(m, forms.size(), forms.c_ptr()); - m_is_utvpi = m_is_dl || is_utvpi_logic(m, forms.size(), forms.c_ptr()); - } - } - - void classify_pred(expr_fast_mark1& mark, app* pred) { - for (unsigned i = 0; i < pred->get_num_args(); ++i) { - quick_for_each_expr(*this, mark, pred->get_arg(i)); - } - } - }; - - void context::validate_proof() { - std::stringstream msg; - proof_ref pr = get_proof(); - proof_checker checker(m); - expr_ref_vector side_conditions(m); - bool ok = checker.check(pr, side_conditions); - if (!ok) { - msg << "proof validation failed"; - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - for (unsigned i = 0; i < side_conditions.size(); ++i) { - expr* cond = side_conditions[i].get(); - expr_ref tmp(m); - - tmp = m.mk_not(cond); - IF_VERBOSE(2, verbose_stream() << "checking side-condition:\n" << mk_pp(cond, m) << "\n";); - smt::kernel solver(m, get_fparams()); - solver.assert_expr(tmp); - lbool res = solver.check(); - if (res != l_false) { - msg << "rule validation failed when checking: " << mk_pp(cond, m); - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - } - } - - void context::validate_search() { - expr_ref tr = m_search.get_trace(*this); - TRACE("pdr", tout << tr << "\n";); - smt::kernel solver(m, get_fparams()); - solver.assert_expr(tr); - lbool res = solver.check(); - if (res != l_true) { - std::stringstream msg; - msg << "rule validation failed when checking: " << tr; - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - } - - void context::validate_model() { - IF_VERBOSE(1, verbose_stream() << "(pdr.validate_model)\n";); - std::stringstream msg; - expr_ref_vector refs(m); - expr_ref tmp(m); - model_ref model; - vector rs; - model_converter_ref mc; - get_level_property(m_inductive_lvl, refs, rs); - inductive_property ex(m, mc, rs); - ex.to_model(model); - var_subst vs(m, false); - expr_free_vars fv; - for (auto const& kv : m_rels) { - ptr_vector const& rules = kv.m_value->rules(); - for (unsigned i = 0; i < rules.size(); ++i) { - datalog::rule& r = *rules[i]; - model->eval(r.get_head(), tmp); - expr_ref_vector fmls(m); - fmls.push_back(m.mk_not(tmp)); - unsigned utsz = r.get_uninterpreted_tail_size(); - unsigned tsz = r.get_tail_size(); - for (unsigned j = 0; j < utsz; ++j) { - model->eval(r.get_tail(j), tmp); - fmls.push_back(tmp); - } - for (unsigned j = utsz; j < tsz; ++j) { - fmls.push_back(r.get_tail(j)); - } - tmp = m.mk_and(fmls.size(), fmls.c_ptr()); - svector names; - fv(tmp); - fv.set_default_sort(m.mk_bool_sort()); - for (unsigned i = 0; i < fv.size(); ++i) { - names.push_back(symbol(i)); - } - fv.reverse(); - if (!fv.empty()) { - tmp = m.mk_exists(fv.size(), fv.c_ptr(), names.c_ptr(), tmp); - } - smt::kernel solver(m, get_fparams()); - solver.assert_expr(tmp); - lbool res = solver.check(); - TRACE("pdr", tout << tmp << " " << res << "\n";); - if (res != l_false) { - msg << "rule validation failed when checking: " << mk_pp(tmp, m); - IF_VERBOSE(0, verbose_stream() << msg.str() << "\n";); - throw default_exception(msg.str()); - } - } - } - } - - void context::validate() { - if (!m_params.pdr_validate_result()) { - return; - } - switch(m_last_result) { - case l_true: - if (m_params.generate_proof_trace()) { - validate_proof(); - } - validate_search(); - break; - case l_false: - validate_model(); - break; - default: - break; - } - } - - void context::reset_core_generalizers() { - std::for_each(m_core_generalizers.begin(), m_core_generalizers.end(), delete_proc()); - m_core_generalizers.reset(); - } - - void context::init_core_generalizers(datalog::rule_set& rules) { - reset_core_generalizers(); - classifier_proc classify(m, rules); - bool use_mc = m_params.pdr_use_multicore_generalizer(); - if (use_mc) { - m_core_generalizers.push_back(alloc(core_multi_generalizer, *this, 0)); - } - if (!classify.is_bool()) { - m.toggle_proof_mode(PGM_ENABLED); - m_fparams.m_arith_bound_prop = BP_NONE; - m_fparams.m_arith_auto_config_simplex = true; - m_fparams.m_arith_propagate_eqs = false; - m_fparams.m_arith_eager_eq_axioms = false; - if (m_params.pdr_utvpi() && - !m_params.pdr_use_convex_closure_generalizer() && - !m_params.pdr_use_convex_interior_generalizer()) { - if (classify.is_dl()) { - m_fparams.m_arith_mode = AS_DIFF_LOGIC; - m_fparams.m_arith_eq2ineq = true; - } - else if (classify.is_utvpi()) { - IF_VERBOSE(1, verbose_stream() << "UTVPI\n";); - m_fparams.m_arith_mode = AS_UTVPI; - m_fparams.m_arith_eq2ineq = true; - } - else { - m_fparams.m_arith_mode = AS_ARITH; - m_fparams.m_arith_eq2ineq = false; - } - } - } - if (m_params.pdr_use_convex_closure_generalizer()) { - m_core_generalizers.push_back(alloc(core_convex_hull_generalizer, *this, true)); - } - if (m_params.pdr_use_convex_interior_generalizer()) { - m_core_generalizers.push_back(alloc(core_convex_hull_generalizer, *this, false)); - } - if (!use_mc && m_params.pdr_use_inductive_generalizer()) { - m_core_generalizers.push_back(alloc(core_bool_inductive_generalizer, *this, 0)); - } - if (m_params.pdr_inductive_reachability_check()) { - m_core_generalizers.push_back(alloc(core_induction_generalizer, *this)); - } - if (m_params.pdr_use_arith_inductive_generalizer()) { - m_core_generalizers.push_back(alloc(core_arith_inductive_generalizer, *this)); - } - - } - - void context::get_level_property(unsigned lvl, expr_ref_vector& res, vector& rs) { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - pred_transformer* r = it->m_value; - if (r->head() == m_query_pred) { - continue; - } - expr_ref conj = r->get_formulas(lvl, false); - m_pm.formula_n2o(0, false, conj); - res.push_back(conj); - ptr_vector sig(r->head()->get_arity(), r->sig()); - rs.push_back(relation_info(m, r->head(), sig, conj)); - } - } - - void context::simplify_formulas() { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - pred_transformer* r = it->m_value; - r->simplify_formulas(); - } - } - - lbool context::solve() { - TRACE("pdr", tout << "solve\n";); - m_last_result = l_undef; - try { - solve_impl(); - UNREACHABLE(); - } - catch (model_exception) { - IF_VERBOSE(1, verbose_stream() << "\n"; m_search.display(verbose_stream());); - m_last_result = l_true; - validate(); - - IF_VERBOSE(1, - if (m_params.print_boogie_certificate()) { - display_certificate(verbose_stream()); - }); - - return l_true; - } - catch (inductive_exception) { - simplify_formulas(); - m_last_result = l_false; - TRACE("pdr", display_certificate(tout);); - IF_VERBOSE(1, { - expr_ref_vector refs(m); - vector rs; - get_level_property(m_inductive_lvl, refs, rs); - model_converter_ref mc; - inductive_property ex(m, mc, rs); - verbose_stream() << ex.to_string(); - }); - - // upgrade invariants that are known to be inductive. - decl2rel::iterator it = m_rels.begin (), end = m_rels.end (); - for (; m_inductive_lvl > 0 && it != end; ++it) { - if (it->m_value->head() != m_query_pred) { - it->m_value->propagate_to_infinity (m_inductive_lvl); - } - } - validate(); - return l_false; - } - catch (unknown_exception) { - return l_undef; - } - UNREACHABLE(); - return l_undef; - } - - void context::checkpoint() { - if (m.canceled()) { - throw default_exception(Z3_CANCELED_MSG); - } - } - - /** - \brief retrieve answer. - */ - expr_ref context::get_answer() { - switch(m_last_result) { - case l_true: return mk_sat_answer(); - case l_false: return mk_unsat_answer(); - default: return expr_ref(m.mk_true(), m); - } - } - - model_ref context::get_model() { - SASSERT(m_last_result == l_false); - expr_ref_vector refs(m); - vector rs; - model_ref md; - get_level_property(m_inductive_lvl, refs, rs); - inductive_property ex(m, m_mc, rs); - ex.to_model(md); - return md; - } - - proof_ref context::get_proof() const { - scoped_proof _sc(m); - proof_ref proof(m); - SASSERT(m_last_result == l_true); - proof = m_search.get_proof_trace(*this); - TRACE("pdr", tout << "PDR trace: " << proof << "\n";); - apply(m, m_pc.get(), proof); - TRACE("pdr", tout << "PDR trace: " << proof << "\n";); - // proof_utils::push_instantiations_up(proof); - // TRACE("pdr", tout << "PDR up: " << proof << "\n";); - return proof; - } - - - /** - \brief Retrieve satisfying assignment with explanation. - */ - expr_ref context::mk_sat_answer() const { - if (m_params.generate_proof_trace()) { - proof_ref pr = get_proof(); - return expr_ref(pr.get(), m); - } - return m_search.get_trace(*this); - } - - expr_ref context::mk_unsat_answer() { - expr_ref_vector refs(m); - vector rs; - get_level_property(m_inductive_lvl, refs, rs); - inductive_property ex(m, const_cast(m_mc), rs); - return ex.to_expr(); - } - - void context::solve_impl() { - if (!m_rels.find(m_query_pred, m_query)) { - throw inductive_exception(); - } - unsigned lvl = 0; - bool reachable; - while (true) { - checkpoint(); - m_expanded_lvl = lvl; - reachable = check_reachability(lvl); - if (reachable) { - throw model_exception(); - } - if (lvl != 0) { - propagate(lvl); - } - lvl++; - m_stats.m_max_depth = std::max(m_stats.m_max_depth, lvl); - IF_VERBOSE(1,verbose_stream() << "Entering level "<level() << "\n";); - checkpoint(); - expand_node(*node); - } - return root->is_closed(); - } - - void context::close_node(model_node& n) { - n.set_closed(); - model_node* p = n.parent(); - while (p && p->is_1closed()) { - p->set_closed(); - p = p->parent(); - } - } - - - void context::expand_node(model_node& n) { - SASSERT(n.is_open()); - expr_ref_vector cube(m); - - if (n.level() < m_expanded_lvl) { - m_expanded_lvl = n.level(); - } - - pred_transformer::scoped_farkas sf (n.pt(), m_params.pdr_farkas()); - if (n.pt().is_reachable(n.state())) { - TRACE("pdr", tout << "reachable\n";); - close_node(n); - } - else { - bool uses_level = true; - switch (expand_state(n, cube, uses_level)) { - case l_true: - if (n.level() == 0) { - TRACE("pdr", n.display(tout << "reachable at level 0\n", 0);); - close_node(n); - } - else { - TRACE("pdr", n.display(tout, 0);); - create_children(n); - } - break; - case l_false: { - core_generalizer::cores cores; - cores.push_back(std::make_pair(cube, uses_level)); - TRACE("pdr", tout << "cube:\n"; - for (unsigned j = 0; j < cube.size(); ++j) tout << mk_pp(cube[j].get(), m) << "\n";); - for (unsigned i = 0; !cores.empty() && i < m_core_generalizers.size(); ++i) { - core_generalizer::cores new_cores; - for (unsigned j = 0; j < cores.size(); ++j) { - (*m_core_generalizers[i])(n, cores[j].first, cores[j].second, new_cores); - } - cores.reset(); - cores.append(new_cores); - } - bool found_invariant = false; - for (unsigned i = 0; i < cores.size(); ++i) { - expr_ref_vector const& core = cores[i].first; - uses_level = cores[i].second; - found_invariant = !uses_level || found_invariant; - expr_ref ncore(m_pm.mk_not_and(core), m); - TRACE("pdr", tout << "invariant state: " << (uses_level?"":"(inductive) ") << mk_pp(ncore, m) << "\n";); - n.pt().add_property(ncore, uses_level?n.level():infty_level); - } - CASSERT("pdr",n.level() == 0 || check_invariant(n.level()-1)); - m_search.backtrack_level(!found_invariant && m_params.pdr_flexible_trace(), n); - break; - } - case l_undef: { - TRACE("pdr", tout << "unknown state: " << mk_pp(m_pm.mk_and(cube), m) << "\n";); - IF_VERBOSE(1, verbose_stream() << "unknown state\n";); - throw unknown_exception(); - } - } - } - } - - // - // check if predicate transformer has a satisfiable predecessor state. - // returns either a satisfiable predecessor state or - // return a property that blocks state and is implied by the - // predicate transformer (or some unfolding of it). - // - lbool context::expand_state(model_node& n, expr_ref_vector& result, bool& uses_level) { - TRACE("pdr", - tout << "expand_state: " << n.pt().head()->get_name(); - tout << " level: " << n.level() << "\n"; - tout << mk_pp(n.state(), m) << "\n";); - - return n.pt().is_reachable(n, &result, uses_level); - } - - void context::propagate(unsigned max_prop_lvl) { - if (m_params.pdr_simplify_formulas_pre()) { - simplify_formulas(); - } - for (unsigned lvl = m_expanded_lvl; lvl <= max_prop_lvl; lvl++) { - checkpoint(); - bool all_propagated = true; - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - checkpoint(); - pred_transformer& r = *it->m_value; - all_propagated = r.propagate_to_next_level(lvl) && all_propagated; - } - CASSERT("pdr", check_invariant(lvl)); - - if (all_propagated && lvl == max_prop_lvl) { - m_inductive_lvl = lvl; - throw inductive_exception(); - } - } - if (m_params.pdr_simplify_formulas_post()) { - simplify_formulas(); - } - } - - - /** - \brief create children states from model cube. - - Introduce the shorthands: - - - T(x0,x1,x) for transition - - phi(x) for n.state() - - M(x0,x1,x) for n.model() - - Assumptions: - M => phi & T - - In other words, - 1. phi & T is implied by M - - Goal is to find phi0(x0), phi1(x1) such that: - - phi(x) & phi0(x0) & phi1(x1) => T(x0, x1, x) - - Strategy: - - - Extract literals from T & phi using ternary simulation with M. - - resulting formula is Phi. - - - perform cheap existential quantifier elimination on - Phi <- exists x . Phi(x0,x1,x) - (e.g., destructive equality resolution) - - - Sub-strategy 1: rename remaining x to fresh variables. - - Sub-strategy 2: replace remaining x to M(x). - - - For each literal L in result: - - - if L is x0 pure, add L to L0 - - if L is x1 pure, add L to L1 - - if L mixes x0, x1, add x1 = M(x1) to L1, add L(x1 |-> M(x1)) to L0 - - - Create sub-goals for L0 and L1. - - */ - void context::create_children(model_node& n) { - SASSERT(n.level() > 0); - bool use_model_generalizer = m_params.pdr_use_model_generalizer(); - scoped_no_proof _sc(m); - - pred_transformer& pt = n.pt(); - model_ref M = n.get_model_ptr(); - SASSERT(M.get()); - datalog::rule const& r = pt.find_rule(*M); - expr* T = pt.get_transition(r); - expr* phi = n.state(); - - n.set_rule(&r); - - - TRACE("pdr", - tout << "Model:\n"; - model_smt2_pp(tout, m, *M, 0); - tout << "\n"; - tout << "Transition:\n" << mk_pp(T, m) << "\n"; - tout << "Phi:\n" << mk_pp(phi, m) << "\n";); - - model_implicant mev(m); - expr_ref_vector mdl(m), forms(m), Phi(m); - forms.push_back(T); - forms.push_back(phi); - flatten_and(forms); - ptr_vector forms1(forms.size(), forms.c_ptr()); - if (use_model_generalizer) { - Phi.append(mev.minimize_model(forms1, M)); - } - else { - Phi.append(mev.minimize_literals(forms1, M)); - } - ptr_vector preds; - pt.find_predecessors(r, preds); - pt.remove_predecessors(Phi); - - app_ref_vector vars(m); - unsigned sig_size = pt.head()->get_arity(); - for (unsigned i = 0; i < sig_size; ++i) { - vars.push_back(m.mk_const(m_pm.o2n(pt.sig(i), 0))); - } - ptr_vector& aux_vars = pt.get_aux_vars(r); - vars.append(aux_vars.size(), aux_vars.c_ptr()); - - scoped_ptr rep; - qe_lite qe(m, m_params.p); - expr_ref phi1 = m_pm.mk_and(Phi); - qe(vars, phi1); - TRACE("pdr", tout << "Eliminated\n" << mk_pp(phi1, m) << "\n";); - if (!use_model_generalizer) { - reduce_disequalities(*M, 3, phi1); - TRACE("pdr", tout << "Reduced-eq\n" << mk_pp(phi1, m) << "\n";); - } - get_context().get_rewriter()(phi1); - - TRACE("pdr", - tout << "Vars:\n"; - for (unsigned i = 0; i < vars.size(); ++i) { - tout << mk_pp(vars[i].get(), m) << "\n"; - } - tout << "Literals\n"; - tout << mk_pp(m_pm.mk_and(Phi), m) << "\n"; - tout << "Reduced\n" << mk_pp(phi1, m) << "\n";); - - if (!vars.empty()) { - // also fresh names for auxiliary variables in body? - expr_substitution sub(m); - expr_ref tmp(m); - proof_ref pr(m); - pr = m.mk_asserted(m.mk_true()); - for (unsigned i = 0; i < vars.size(); ++i) { - tmp = mev.eval(M, vars[i].get()); - sub.insert(vars[i].get(), tmp, pr); - } - if (!rep) rep = mk_expr_simp_replacer(m); - rep->set_substitution(&sub); - (*rep)(phi1); - TRACE("pdr", tout << "Projected:\n" << mk_pp(phi1, m) << "\n";); - } - Phi.reset(); - flatten_and(phi1, Phi); - unsigned_vector indices; - vector child_states; - child_states.resize(preds.size(), expr_ref_vector(m)); - for (unsigned i = 0; i < Phi.size(); ++i) { - m_pm.collect_indices(Phi[i].get(), indices); - if (indices.size() == 0) { - IF_VERBOSE(3, verbose_stream() << "Skipping " << mk_pp(Phi[i].get(), m) << "\n";); - } - else if (indices.size() == 1) { - child_states[indices.back()].push_back(Phi[i].get()); - } - else { - expr_substitution sub(m); - expr_ref tmp(m); - proof_ref pr(m); - pr = m.mk_asserted(m.mk_true()); - vector > vars; - m_pm.collect_variables(Phi[i].get(), vars); - SASSERT(vars.size() == indices.back()+1); - for (unsigned j = 1; j < indices.size(); ++j) { - ptr_vector const& vs = vars[indices[j]]; - for (unsigned k = 0; k < vs.size(); ++k) { - tmp = mev.eval(M, vs[k]); - sub.insert(vs[k], tmp, pr); - child_states[indices[j]].push_back(m.mk_eq(vs[k], tmp)); - } - } - tmp = Phi[i].get(); - if (!rep) rep = mk_expr_simp_replacer(m); - rep->set_substitution(&sub); - (*rep)(tmp); - child_states[indices[0]].push_back(tmp); - } - - } - - expr_ref n_cube(m); - for (unsigned i = 0; i < preds.size(); ++i) { - pred_transformer& pt = *m_rels.find(preds[i]); - SASSERT(pt.head() == preds[i]); - expr_ref o_cube = m_pm.mk_and(child_states[i]); - m_pm.formula_o2n(o_cube, n_cube, i); - model_node* child = alloc(model_node, &n, n_cube, pt, n.level()-1); - ++m_stats.m_num_nodes; - m_search.add_leaf(*child); - IF_VERBOSE(2, verbose_stream() << "Predecessor: " << mk_pp(n_cube, m) << " " << (child->is_closed()?"closed":"open") << "\n";); - m_stats.m_max_depth = std::max(m_stats.m_max_depth, child->depth()); - } - n.check_pre_closed(); - TRACE("pdr", m_search.display(tout);); - } - - void context::collect_statistics(statistics& st) const { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (it = m_rels.begin(); it != end; ++it) { - it->m_value->collect_statistics(st); - } - st.update("PDR num unfoldings", m_stats.m_num_nodes); - st.update("PDR max depth", m_stats.m_max_depth); - st.update("PDR inductive level", m_inductive_lvl); - m_pm.collect_statistics(st); - - for (unsigned i = 0; i < m_core_generalizers.size(); ++i) { - m_core_generalizers[i]->collect_statistics(st); - } - } - - void context::reset_statistics() { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (it = m_rels.begin(); it != end; ++it) { - it->m_value->reset_statistics(); - } - m_stats.reset(); - m_pm.reset_statistics(); - - for (unsigned i = 0; i < m_core_generalizers.size(); ++i) { - m_core_generalizers[i]->reset_statistics(); - } - - } - - - std::ostream& context::display(std::ostream& out) const { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - it->m_value->display(out); - } - m_search.display(out); - return out; - } - - bool context::check_invariant(unsigned lvl) { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - checkpoint(); - if (!check_invariant(lvl, it->m_key)) { - return false; - } - } - return true; - } - - bool context::check_invariant(unsigned lvl, func_decl* fn) { - smt::kernel ctx(m, m_fparams); - pred_transformer& pt = *m_rels.find(fn); - expr_ref_vector conj(m); - expr_ref inv = pt.get_formulas(next_level(lvl), false); - if (m.is_true(inv)) return true; - pt.add_premises(m_rels, lvl, conj); - conj.push_back(m.mk_not(inv)); - expr_ref fml(m.mk_and(conj.size(), conj.c_ptr()), m); - ctx.assert_expr(fml); - lbool result = ctx.check(); - TRACE("pdr", tout << "Check invariant level: " << lvl << " " << result << "\n" << mk_pp(fml, m) << "\n";); - return result == l_false; - } - - void context::display_certificate(std::ostream& strm) { - switch(m_last_result) { - case l_false: { - expr_ref_vector refs(m); - vector rs; - get_level_property(m_inductive_lvl, refs, rs); - inductive_property ex(m, const_cast(m_mc), rs); - strm << ex.to_string(); - break; - } - case l_true: { - if (m_params.print_boogie_certificate()) { - datalog::boogie_proof bp(m); - bp.set_proof(get_proof()); - bp.set_model(nullptr); - bp.pp(strm); - } - else { - strm << mk_pp(mk_sat_answer(), m); - } - break; - } - case l_undef: { - strm << "unknown"; - break; - } - } - } - -} diff --git a/src/muz/pdr/pdr_context.h b/src/muz/pdr/pdr_context.h deleted file mode 100644 index ebaa3f257..000000000 --- a/src/muz/pdr/pdr_context.h +++ /dev/null @@ -1,448 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_context.h - -Abstract: - - PDR for datalog - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-20. - -Revision History: - ---*/ - -#ifndef PDR_CONTEXT_H_ -#define PDR_CONTEXT_H_ - -#ifdef _CYGWIN -#undef min -#undef max -#endif -#include -#include "muz/pdr/pdr_manager.h" -#include "muz/pdr/pdr_prop_solver.h" -#include "muz/pdr/pdr_reachable_cache.h" -#include "muz/base/fixedpoint_params.hpp" - - -namespace datalog { - class rule_set; - class context; -}; - -namespace pdr { - - class pred_transformer; - class model_node; - class context; - - typedef obj_map rule2inst; - typedef obj_map decl2rel; - - - // - // Predicate transformer state. - // A predicate transformer corresponds to the - // set of rules that have the same head predicates. - // - - class pred_transformer { - - struct stats { - unsigned m_num_propagations; - stats() { reset(); } - void reset() { memset(this, 0, sizeof(*this)); } - }; - - typedef obj_map rule2expr; - typedef obj_map > rule2apps; - - manager& pm; // pdr-manager - ast_manager& m; // manager - context& ctx; - - func_decl_ref m_head; // predicate - func_decl_ref_vector m_sig; // signature - ptr_vector m_use; // places where 'this' is referenced. - ptr_vector m_rules; // rules used to derive transformer - prop_solver m_solver; // solver context - vector m_levels; // level formulas - expr_ref_vector m_invariants; // properties that are invariant. - obj_map m_prop2level; // map property to level where it occurs. - obj_map m_tag2rule; // map tag predicate to rule. - rule2expr m_rule2tag; // map rule to predicate tag. - rule2inst m_rule2inst; // map rules to instantiations of indices - rule2expr m_rule2transition; // map rules to transition - rule2apps m_rule2vars; // map rule to auxiliary variables - expr_ref m_transition; // transition relation. - expr_ref m_initial_state; // initial state. - reachable_cache m_reachable; - ptr_vector m_predicates; - stats m_stats; - - void init_sig(); - void ensure_level(unsigned level); - bool add_property1(expr * lemma, unsigned lvl); // add property 'p' to state at level lvl. - void add_child_property(pred_transformer& child, expr* lemma, unsigned lvl); - void mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result); - - // Initialization - void init_rules(decl2rel const& pts, expr_ref& init, expr_ref& transition); - void init_rule(decl2rel const& pts, datalog::rule const& rule, expr_ref& init, - ptr_vector& rules, expr_ref_vector& transition); - void init_atom(decl2rel const& pts, app * atom, app_ref_vector& var_reprs, expr_ref_vector& conj, unsigned tail_idx); - - void simplify_formulas(tactic& tac, expr_ref_vector& fmls); - - // Debugging - bool check_filled(app_ref_vector const& v) const; - - void add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r); - - public: - pred_transformer(context& ctx, manager& pm, func_decl* head); - ~pred_transformer(); - - void add_rule(datalog::rule* r) { m_rules.push_back(r); } - void add_use(pred_transformer* pt) { if (!m_use.contains(pt)) m_use.insert(pt); } - void initialize(decl2rel const& pts); - - func_decl* head() const { return m_head; } - ptr_vector const& rules() const { return m_rules; } - func_decl* sig(unsigned i) { init_sig(); return m_sig[i].get(); } // signature - func_decl* const* sig() { init_sig(); return m_sig.c_ptr(); } - unsigned sig_size() { init_sig(); return m_sig.size(); } - expr* transition() const { return m_transition; } - expr* initial_state() const { return m_initial_state; } - expr* rule2tag(datalog::rule const* r) { return m_rule2tag.find(r); } - unsigned get_num_levels() { return m_levels.size(); } - expr_ref get_cover_delta(func_decl* p_orig, int level); - void add_cover(unsigned level, expr* property); - context& get_context() { return ctx; } - - std::ostream& display(std::ostream& strm) const; - - void collect_statistics(statistics& st) const; - void reset_statistics(); - - bool is_reachable(expr* state); - void remove_predecessors(expr_ref_vector& literals); - void find_predecessors(datalog::rule const& r, ptr_vector& predicates) const; - datalog::rule const& find_rule(model_core const& model) const; - expr* get_transition(datalog::rule const& r) { return m_rule2transition.find(&r); } - ptr_vector& get_aux_vars(datalog::rule const& r) { return m_rule2vars.find(&r); } - - bool propagate_to_next_level(unsigned level); - void propagate_to_infinity(unsigned level); - void add_property(expr * lemma, unsigned lvl); // add property 'p' to state at level. - - lbool is_reachable(model_node& n, expr_ref_vector* core, bool& uses_level); - bool is_invariant(unsigned level, expr* co_state, bool inductive, bool& assumes_level, expr_ref_vector* core = nullptr); - bool check_inductive(unsigned level, expr_ref_vector& state, bool& assumes_level); - - expr_ref get_formulas(unsigned level, bool add_axioms); - - void simplify_formulas(); - - expr_ref get_propagation_formula(decl2rel const& pts, unsigned level); - - manager& get_pdr_manager() const { return pm; } - ast_manager& get_manager() const { return m; } - - void add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r); - - void close(expr* e); - - app_ref_vector& get_inst(datalog::rule const* r) { return *m_rule2inst.find(r);} - - void inherit_properties(pred_transformer& other); - - void ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector& aux_vars); - - prop_solver& get_solver() { return m_solver; } - prop_solver const& get_solver() const { return m_solver; } - - void set_use_farkas(bool f) { get_solver().set_use_farkas(f); } - bool get_use_farkas() const { return get_solver().get_use_farkas(); } - class scoped_farkas { - bool m_old; - pred_transformer& m_p; - public: - scoped_farkas(pred_transformer& p, bool v): m_old(p.get_use_farkas()), m_p(p) { - p.set_use_farkas(v); - } - ~scoped_farkas() { m_p.set_use_farkas(m_old); } - }; - - }; - - - // structure for counter-example search. - class model_node { - model_node* m_parent; - model_node* m_next; - model_node* m_prev; - pred_transformer& m_pt; - expr_ref m_state; - model_ref m_model; - ptr_vector m_children; - unsigned m_level; - unsigned m_orig_level; - unsigned m_depth; - bool m_closed; - datalog::rule const* m_rule; - public: - model_node(model_node* parent, expr_ref& state, pred_transformer& pt, unsigned level): - m_parent(parent), m_next(nullptr), m_prev(nullptr), m_pt(pt), m_state(state), m_model(nullptr), - m_level(level), m_orig_level(level), m_depth(0), m_closed(false), m_rule(nullptr) { - model_node* p = m_parent; - if (p) { - p->m_children.push_back(this); - SASSERT(p->m_level == level+1); - SASSERT(p->m_level > 0); - m_depth = p->m_depth+1; - if (p && p->is_closed()) { - p->set_open(); - } - } - } - void set_model(model_ref& m) { m_model = m; } - unsigned level() const { return m_level; } - unsigned orig_level() const { return m_orig_level; } - unsigned depth() const { return m_depth; } - void increase_level() { ++m_level; } - expr_ref const& state() const { return m_state; } - ptr_vector const& children() { return m_children; } - pred_transformer& pt() const { return m_pt; } - model_node* parent() const { return m_parent; } - model* get_model_ptr() const { return m_model.get(); } - model const& get_model() const { return *m_model; } - unsigned index() const; - - bool is_closed() const { return m_closed; } - bool is_open() const { return !is_closed(); } - - bool is_1closed() { - if (is_closed()) return true; - if (m_children.empty()) return false; - for (unsigned i = 0; i < m_children.size(); ++i) { - if (m_children[i]->is_open()) return false; - } - return true; - } - - void check_pre_closed(); - void set_closed(); - void set_open(); - void set_pre_closed() { TRACE("pdr", tout << state() << "\n";); m_closed = true; } - void reset() { m_children.reset(); } - - void set_rule(datalog::rule const* r) { m_rule = r; } - datalog::rule* get_rule(); - - void mk_instantiate(datalog::rule_ref& r0, datalog::rule_ref& r1, expr_ref_vector& binding); - - std::ostream& display(std::ostream& out, unsigned indent); - - void dequeue(model_node*& root); - void enqueue(model_node* n); - model_node* next() const { return m_next; } - bool is_goal() const { return nullptr != next(); } - }; - - class model_search { - typedef ptr_vector model_nodes; - bool m_bfs; - model_node* m_root; - model_node* m_goal; - vector > m_cache; - obj_map& cache(model_node const& n); - void erase_children(model_node& n, bool backtrack); - void remove_node(model_node& n, bool backtrack); - void enqueue_leaf(model_node* n); // add leaf to priority queue. - void update_models(); - void set_leaf(model_node& n); // Set node as leaf, remove children. - unsigned num_goals() const; - - public: - model_search(bool bfs): m_bfs(bfs), m_root(nullptr), m_goal(nullptr) {} - ~model_search(); - - void reset(); - model_node* next(); - void add_leaf(model_node& n); // add fresh node. - - void set_root(model_node* n); - model_node& get_root() const { return *m_root; } - std::ostream& display(std::ostream& out) const; - expr_ref get_trace(context const& ctx); - proof_ref get_proof_trace(context const& ctx); - void backtrack_level(bool uses_level, model_node& n); - void remove_goal(model_node& n); - - void well_formed(); - }; - - struct model_exception { }; - struct inductive_exception {}; - - - // 'state' is unsatisfiable at 'level' with 'core'. - // Minimize or weaken core. - class core_generalizer { - protected: - context& m_ctx; - public: - typedef vector > cores; - core_generalizer(context& ctx): m_ctx(ctx) {} - virtual ~core_generalizer() {} - virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) = 0; - virtual void operator()(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) { - new_cores.push_back(std::make_pair(core, uses_level)); - if (!core.empty()) { - (*this)(n, new_cores.back().first, new_cores.back().second); - } - } - virtual void collect_statistics(statistics& st) const {} - virtual void reset_statistics() {} - }; - - class context { - - struct stats { - unsigned m_num_nodes; - unsigned m_max_depth; - stats() { reset(); } - void reset() { memset(this, 0, sizeof(*this)); } - }; - - smt_params& m_fparams; - fixedpoint_params const& m_params; - ast_manager& m; - datalog::context* m_context; - manager m_pm; - decl2rel m_rels; // Map from relation predicate to fp-operator. - decl2rel m_rels_tmp; - func_decl_ref m_query_pred; - pred_transformer* m_query; - mutable model_search m_search; - lbool m_last_result; - unsigned m_inductive_lvl; - unsigned m_expanded_lvl; - ptr_vector m_core_generalizers; - stats m_stats; - model_converter_ref m_mc; - proof_converter_ref m_pc; - - // Functions used by search. - void solve_impl(); - bool check_reachability(unsigned level); - void propagate(unsigned max_prop_lvl); - void close_node(model_node& n); - void check_pre_closed(model_node& n); - void expand_node(model_node& n); - lbool expand_state(model_node& n, expr_ref_vector& cube, bool& uses_level); - void create_children(model_node& n); - expr_ref mk_sat_answer() const; - expr_ref mk_unsat_answer(); - - // Generate inductive property - void get_level_property(unsigned lvl, expr_ref_vector& res, vector & rs); - - - // Initialization - class classifier_proc; - void init_core_generalizers(datalog::rule_set& rules); - - bool check_invariant(unsigned lvl); - bool check_invariant(unsigned lvl, func_decl* fn); - - void checkpoint(); - - void init_rules(datalog::rule_set& rules, decl2rel& transformers); - - void simplify_formulas(); - - void reset_core_generalizers(); - - void reset(decl2rel& rels); - - void validate(); - void validate_proof(); - void validate_search(); - void validate_model(); - - public: - - /** - Initial values of predicates are stored in corresponding relations in dctx. - - We check whether there is some reachable state of the relation checked_relation. - */ - context( - smt_params& fparams, - fixedpoint_params const& params, - ast_manager& m); - - ~context(); - - smt_params& get_fparams() const { return m_fparams; } - fixedpoint_params const& get_params() const { return m_params; } - ast_manager& get_manager() const { return m; } - manager& get_pdr_manager() { return m_pm; } - decl2rel const& get_pred_transformers() const { return m_rels; } - pred_transformer& get_pred_transformer(func_decl* p) const { return *m_rels.find(p); } - datalog::context& get_context() const { SASSERT(m_context); return *m_context; } - expr_ref get_answer(); - - bool is_dl() const { return m_fparams.m_arith_mode == AS_DIFF_LOGIC; } - bool is_utvpi() const { return m_fparams.m_arith_mode == AS_UTVPI; } - - void collect_statistics(statistics& st) const; - void reset_statistics(); - - std::ostream& display(std::ostream& strm) const; - - void display_certificate(std::ostream& strm); - - lbool solve(); - - void reset(bool full = true); - - void set_query(func_decl* q) { m_query_pred = q; } - - void set_unsat() { m_last_result = l_false; } - - void set_model_converter(model_converter_ref& mc) { m_mc = mc; } - - model_converter_ref get_model_converter() { return m_mc; } - - void set_proof_converter(proof_converter_ref& pc) { m_pc = pc; } - - void update_rules(datalog::rule_set& rules); - - void set_axioms(expr* axioms) { m_pm.set_background(axioms); } - - unsigned get_num_levels(func_decl* p); - - expr_ref get_cover_delta(int level, func_decl* p_orig, func_decl* p); - - void add_cover(int level, func_decl* pred, expr* property); - - model_ref get_model(); - - proof_ref get_proof() const; - - model_node& get_root() const { return m_search.get_root(); } - - }; - -}; - -#endif diff --git a/src/muz/pdr/pdr_dl_interface.cpp b/src/muz/pdr/pdr_dl_interface.cpp deleted file mode 100644 index 27ce0500c..000000000 --- a/src/muz/pdr/pdr_dl_interface.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_dl.cpp - -Abstract: - - SMT2 interface for the datalog PDR - -Author: - - Krystof Hoder (t-khoder) 2011-9-22. - -Revision History: - ---*/ - -#include "muz/base/dl_context.h" -#include "muz/transforms/dl_mk_coi_filter.h" -#include "muz/base/dl_rule.h" -#include "muz/base/dl_rule_transformer.h" -#include "muz/pdr/pdr_context.h" -#include "muz/pdr/pdr_dl_interface.h" -#include "muz/base/dl_rule_set.h" -#include "muz/transforms/dl_mk_slice.h" -#include "muz/transforms/dl_mk_unfold.h" -#include "muz/transforms/dl_mk_coalesce.h" -#include "muz/transforms/dl_transforms.h" -#include "ast/scoped_proof.h" -#include "model/model_smt2_pp.h" - -using namespace pdr; - -dl_interface::dl_interface(datalog::context& ctx) : - engine_base(ctx.get_manager(), "pdr"), - m_ctx(ctx), - m_pdr_rules(ctx), - m_old_rules(ctx), - m_context(nullptr), - m_refs(ctx.get_manager()) { - m_context = alloc(pdr::context, ctx.get_fparams(), ctx.get_params(), ctx.get_manager()); -} - - -dl_interface::~dl_interface() { - dealloc(m_context); -} - - -// -// Check if the new rules are weaker so that we can -// re-use existing context. -// -void dl_interface::check_reset() { - datalog::rule_set const& new_rules = m_ctx.get_rules(); - datalog::rule_ref_vector const& old_rules = m_old_rules.get_rules(); - bool is_subsumed = !old_rules.empty(); - for (unsigned i = 0; is_subsumed && i < new_rules.get_num_rules(); ++i) { - is_subsumed = false; - for (unsigned j = 0; !is_subsumed && j < old_rules.size(); ++j) { - if (m_ctx.check_subsumes(*old_rules[j], *new_rules.get_rule(i))) { - is_subsumed = true; - } - } - if (!is_subsumed) { - TRACE("pdr", new_rules.get_rule(i)->display(m_ctx, tout << "Fresh rule ");); - m_context->reset(); - } - } - m_old_rules.replace_rules(new_rules); -} - - -lbool dl_interface::query(expr * query) { - //we restore the initial state in the datalog context - m_ctx.ensure_opened(); - m_refs.reset(); - m_pred2slice.reset(); - ast_manager& m = m_ctx.get_manager(); - datalog::rule_manager& rm = m_ctx.get_rule_manager(); - datalog::rule_set& rules0 = m_ctx.get_rules(); - - datalog::rule_set old_rules(rules0); - func_decl_ref query_pred(m); - rm.mk_query(query, rules0); - expr_ref bg_assertion = m_ctx.get_background_assertion(); - - check_reset(); - - TRACE("pdr", - if (!m.is_true(bg_assertion)) { - tout << "axioms:\n"; - tout << mk_pp(bg_assertion, m) << "\n"; - } - tout << "query: " << mk_pp(query, m) << "\n"; - tout << "rules:\n"; - m_ctx.display_rules(tout); - ); - - - apply_default_transformation(m_ctx); - - if (m_ctx.get_params().xform_slice()) { - datalog::rule_transformer transformer(m_ctx); - datalog::mk_slice* slice = alloc(datalog::mk_slice, m_ctx); - transformer.register_plugin(slice); - m_ctx.transform_rules(transformer); - - // track sliced predicates. - obj_map const& preds = slice->get_predicates(); - obj_map::iterator it = preds.begin(); - obj_map::iterator end = preds.end(); - for (; it != end; ++it) { - m_pred2slice.insert(it->m_key, it->m_value); - m_refs.push_back(it->m_key); - m_refs.push_back(it->m_value); - } - } - - if (m_ctx.get_params().xform_unfold_rules() > 0) { - unsigned num_unfolds = m_ctx.get_params().xform_unfold_rules(); - datalog::rule_transformer transf1(m_ctx), transf2(m_ctx); - transf1.register_plugin(alloc(datalog::mk_coalesce, m_ctx)); - transf2.register_plugin(alloc(datalog::mk_unfold, m_ctx)); - if (m_ctx.get_params().xform_coalesce_rules()) { - m_ctx.transform_rules(transf1); - } - while (num_unfolds > 0) { - m_ctx.transform_rules(transf2); - --num_unfolds; - } - } - - const datalog::rule_set& rules = m_ctx.get_rules(); - if (rules.get_output_predicates().empty()) { - m_context->set_unsat(); - return l_false; - } - - query_pred = rules.get_output_predicate(); - - TRACE("pdr", - tout << "rules:\n"; - m_ctx.display_rules(tout); - m_ctx.display_smt2(0, 0, tout); - ); - - IF_VERBOSE(2, m_ctx.display_rules(verbose_stream());); - m_pdr_rules.replace_rules(rules); - m_pdr_rules.close(); - m_ctx.record_transformed_rules(); - m_ctx.reopen(); - m_ctx.replace_rules(old_rules); - - - scoped_restore_proof _sc(m); // update_rules may overwrite the proof mode. - - m_context->set_proof_converter(m_ctx.get_proof_converter()); - m_context->set_model_converter(m_ctx.get_model_converter()); - m_context->set_query(query_pred); - m_context->set_axioms(bg_assertion); - m_context->update_rules(m_pdr_rules); - - if (m_pdr_rules.get_rules().empty()) { - m_context->set_unsat(); - IF_VERBOSE(1, model_smt2_pp(verbose_stream(), m, *m_context->get_model(),0);); - return l_false; - } - - return m_context->solve(); - -} - -expr_ref dl_interface::get_cover_delta(int level, func_decl* pred_orig) { - func_decl* pred = pred_orig; - m_pred2slice.find(pred_orig, pred); - SASSERT(pred); - return m_context->get_cover_delta(level, pred_orig, pred); -} - -void dl_interface::add_cover(int level, func_decl* pred, expr* property) { - if (m_ctx.get_params().xform_slice()) { - throw default_exception("Covers are incompatible with slicing. Disable slicing before using covers"); - } - m_context->add_cover(level, pred, property); -} - -unsigned dl_interface::get_num_levels(func_decl* pred) { - m_pred2slice.find(pred, pred); - SASSERT(pred); - return m_context->get_num_levels(pred); -} - -void dl_interface::collect_statistics(statistics& st) const { - m_context->collect_statistics(st); -} - -void dl_interface::reset_statistics() { - m_context->reset_statistics(); -} - -void dl_interface::display_certificate(std::ostream& out) const { - m_context->display_certificate(out); -} - -expr_ref dl_interface::get_answer() { - return m_context->get_answer(); -} - - - -void dl_interface::updt_params() { - dealloc(m_context); - m_context = alloc(pdr::context, m_ctx.get_fparams(), m_ctx.get_params(), m_ctx.get_manager()); -} - -model_ref dl_interface::get_model() { - return m_context->get_model(); -} - -proof_ref dl_interface::get_proof() { - return m_context->get_proof(); -} diff --git a/src/muz/pdr/pdr_dl_interface.h b/src/muz/pdr/pdr_dl_interface.h deleted file mode 100644 index 1a0b04635..000000000 --- a/src/muz/pdr/pdr_dl_interface.h +++ /dev/null @@ -1,78 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_dl_interface.h - -Abstract: - - SMT2 interface for the datalog PDR - -Author: - - Krystof Hoder (t-khoder) 2011-9-22. - -Revision History: - ---*/ - -#ifndef PDR_DL_INTERFACE_H_ -#define PDR_DL_INTERFACE_H_ - -#include "util/lbool.h" -#include "muz/base/dl_rule.h" -#include "muz/base/dl_rule_set.h" -#include "muz/base/dl_util.h" -#include "muz/base/dl_engine_base.h" -#include "util/statistics.h" - -namespace datalog { - class context; -} - -namespace pdr { - - class context; - - class dl_interface : public datalog::engine_base { - datalog::context& m_ctx; - datalog::rule_set m_pdr_rules; - datalog::rule_set m_old_rules; - context* m_context; - obj_map m_pred2slice; - ast_ref_vector m_refs; - - void check_reset(); - - public: - dl_interface(datalog::context& ctx); - ~dl_interface() override; - - lbool query(expr* query) override; - - void display_certificate(std::ostream& out) const override; - - void collect_statistics(statistics& st) const override; - - void reset_statistics() override; - - expr_ref get_answer() override; - - unsigned get_num_levels(func_decl* pred) override; - - expr_ref get_cover_delta(int level, func_decl* pred) override; - - void add_cover(int level, func_decl* pred, expr* property) override; - - void updt_params() override; - - model_ref get_model() override; - - proof_ref get_proof() override; - - }; -} - - -#endif diff --git a/src/muz/pdr/pdr_farkas_learner.cpp b/src/muz/pdr/pdr_farkas_learner.cpp deleted file mode 100644 index 6695788c2..000000000 --- a/src/muz/pdr/pdr_farkas_learner.cpp +++ /dev/null @@ -1,1012 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_farkas_learner.cpp - -Abstract: - - Proviced abstract interface and some inplementations of algorithms - for strenghtning lemmas - -Author: - - Krystof Hoder (t-khoder) 2011-11-1. - -Revision History: - ---*/ - -#include "ast/ast_smt2_pp.h" -#include "ast/array_decl_plugin.h" -#include "ast/rewriter/bool_rewriter.h" -#include "ast/dl_decl_plugin.h" -#include "ast/for_each_expr.h" -#include "muz/base/dl_util.h" -#include "ast/rewriter/rewriter.h" -#include "ast/rewriter/rewriter_def.h" -#include "muz/pdr/pdr_util.h" -#include "muz/pdr/pdr_farkas_learner.h" -#include "ast/rewriter/th_rewriter.h" -#include "ast/ast_ll_pp.h" -#include "tactic/arith/arith_bounds_tactic.h" -#include "ast/proofs/proof_utils.h" -#include "ast/reg_decl_plugins.h" - - -namespace pdr { - - class farkas_learner::constr { - - ast_manager& m; - arith_util a; - app_ref_vector m_ineqs; - vector m_coeffs; - - unsigned m_time; - unsigned_vector m_roots, m_size, m_his, m_reps, m_ts; - - void mk_coerce(expr*& e1, expr*& e2) { - if (a.is_int(e1) && a.is_real(e2)) { - e1 = a.mk_to_real(e1); - } - else if (a.is_int(e2) && a.is_real(e1)) { - e2 = a.mk_to_real(e2); - } - } - - app* mk_add(expr* e1, expr* e2) { - mk_coerce(e1, e2); - return a.mk_add(e1, e2); - } - - app* mk_mul(expr* e1, expr* e2) { - mk_coerce(e1, e2); - return a.mk_mul(e1, e2); - } - - app* mk_le(expr* e1, expr* e2) { - mk_coerce(e1, e2); - return a.mk_le(e1, e2); - } - - app* mk_ge(expr* e1, expr* e2) { - mk_coerce(e1, e2); - return a.mk_ge(e1, e2); - } - - app* mk_gt(expr* e1, expr* e2) { - mk_coerce(e1, e2); - return a.mk_gt(e1, e2); - } - - app* mk_lt(expr* e1, expr* e2) { - mk_coerce(e1, e2); - return a.mk_lt(e1, e2); - } - - void mul(rational const& c, expr* e, expr_ref& res) { - expr_ref tmp(m); - if (c.is_one()) { - tmp = e; - } - else { - tmp = mk_mul(a.mk_numeral(c, c.is_int() && a.is_int(e)), e); - } - res = mk_add(res, tmp); - } - - bool is_int_sort(app* c) { - SASSERT(m.is_eq(c) || a.is_le(c) || a.is_lt(c) || a.is_gt(c) || a.is_ge(c)); - SASSERT(a.is_int(c->get_arg(0)) || a.is_real(c->get_arg(0))); - return a.is_int(c->get_arg(0)); - } - - bool is_int_sort() { - SASSERT(!m_ineqs.empty()); - return is_int_sort(m_ineqs[0].get()); - } - - void normalize_coeffs() { - rational l(1); - for (unsigned i = 0; i < m_coeffs.size(); ++i) { - l = lcm(l, denominator(m_coeffs[i])); - } - if (!l.is_one()) { - for (unsigned i = 0; i < m_coeffs.size(); ++i) { - m_coeffs[i] *= l; - } - } - } - - app* mk_one() { - return a.mk_numeral(rational(1), true); - } - - app* fix_sign(bool is_pos, app* c) { - expr* x, *y; - SASSERT(m.is_eq(c) || a.is_le(c) || a.is_lt(c) || a.is_gt(c) || a.is_ge(c)); - bool is_int = is_int_sort(c); - if (is_int && is_pos && (a.is_lt(c, x, y) || a.is_gt(c, y, x))) { - return mk_le(mk_add(x, mk_one()), y); - } - if (is_int && !is_pos && (a.is_le(c, x, y) || a.is_ge(c, y, x))) { - // !(x <= y) <=> x > y <=> x >= y + 1 - return mk_ge(x, mk_add(y, mk_one())); - } - if (is_pos) { - return c; - } - if (a.is_le(c, x, y)) return mk_gt(x, y); - if (a.is_lt(c, x, y)) return mk_ge(x, y); - if (a.is_ge(c, x, y)) return mk_lt(x, y); - if (a.is_gt(c, x, y)) return mk_le(x, y); - UNREACHABLE(); - return c; - } - - public: - constr(ast_manager& m) : m(m), a(m), m_ineqs(m), m_time(0) {} - - void reset() { - m_ineqs.reset(); - m_coeffs.reset(); - } - - /** add a multiple of constraint c to the current constr */ - void add(rational const & coef, app * c) { - bool is_pos = true; - expr* e; - while (m.is_not(c, e)) { - is_pos = !is_pos; - c = to_app(e); - } - - if (!coef.is_zero() && !m.is_true(c)) { - m_coeffs.push_back(coef); - m_ineqs.push_back(fix_sign(is_pos, c)); - } - } - - // - // Extract the complement of premises multiplied by Farkas coefficients. - // - void get(expr_ref& res) { - if (m_coeffs.empty()) { - res = m.mk_false(); - return; - } - bool is_int = is_int_sort(); - if (is_int) { - normalize_coeffs(); - } - TRACE("pdr", - for (unsigned i = 0; i < m_coeffs.size(); ++i) { - tout << m_coeffs[i] << ": " << mk_pp(m_ineqs[i].get(), m) << "\n"; - } - ); - - res = extract_consequence(0, m_coeffs.size()); - -#if 1 - // partition equalities into variable disjoint sets. - // take the conjunction of these instead of the - // linear combination. - partition_ineqs(); - expr_ref_vector lits(m); - unsigned lo = 0; - for (unsigned i = 0; i < m_his.size(); ++i) { - unsigned hi = m_his[i]; - lits.push_back(extract_consequence(lo, hi)); - lo = hi; - } - res = mk_or(lits); - IF_VERBOSE(2, { if (lits.size() > 1) { verbose_stream() << "combined lemma: " << mk_pp(res, m) << "\n"; } }); -#endif - } - - private: - - // partition inequalities into variable disjoint sets. - void partition_ineqs() { - m_reps.reset(); - m_his.reset(); - ++m_time; - for (unsigned i = 0; i < m_ineqs.size(); ++i) { - m_reps.push_back(process_term(m_ineqs[i].get())); - } - unsigned head = 0; - while (head < m_ineqs.size()) { - unsigned r = find(m_reps[head]); - unsigned tail = head; - for (unsigned i = head+1; i < m_ineqs.size(); ++i) { - if (find(m_reps[i]) == r) { - ++tail; - if (tail != i) { - SASSERT(tail < i); - std::swap(m_reps[tail], m_reps[i]); - app_ref tmp(m); - tmp = m_ineqs[i].get(); - m_ineqs[i] = m_ineqs[tail].get(); - m_ineqs[tail] = tmp; - std::swap(m_coeffs[tail], m_coeffs[i]); - } - } - } - head = tail + 1; - m_his.push_back(head); - } - } - - unsigned find(unsigned idx) { - if (m_ts.size() <= idx) { - m_roots.resize(idx+1); - m_size.resize(idx+1); - m_ts.resize(idx+1); - m_roots[idx] = idx; - m_ts[idx] = m_time; - m_size[idx] = 1; - return idx; - } - if (m_ts[idx] != m_time) { - m_size[idx] = 1; - m_ts[idx] = m_time; - m_roots[idx] = idx; - return idx; - } - while (true) { - if (m_roots[idx] == idx) { - return idx; - } - idx = m_roots[idx]; - } - } - - void merge(unsigned i, unsigned j) { - i = find(i); - j = find(j); - if (i == j) { - return; - } - if (m_size[i] > m_size[j]) { - std::swap(i, j); - } - m_roots[i] = j; - m_size[j] += m_size[i]; - } - - unsigned process_term(expr* e) { - unsigned r = e->get_id(); - ptr_vector todo; - ast_mark mark; - todo.push_back(e); - while (!todo.empty()) { - e = todo.back(); - todo.pop_back(); - if (mark.is_marked(e)) { - continue; - } - mark.mark(e, true); - if (is_uninterp(e)) { - merge(r, e->get_id()); - } - if (is_app(e)) { - app* a = to_app(e); - for (unsigned i = 0; i < a->get_num_args(); ++i) { - todo.push_back(a->get_arg(i)); - } - } - } - return r; - } - - expr_ref extract_consequence(unsigned lo, unsigned hi) { - bool is_int = is_int_sort(); - app_ref zero(a.mk_numeral(rational::zero(), is_int), m); - expr_ref res(m); - res = zero; - bool is_strict = false; - bool is_eq = true; - expr* x, *y; - for (unsigned i = lo; i < hi; ++i) { - app* c = m_ineqs[i].get(); - if (m.is_eq(c, x, y)) { - mul(m_coeffs[i], x, res); - mul(-m_coeffs[i], y, res); - } - if (a.is_lt(c, x, y) || a.is_gt(c, y, x)) { - mul(m_coeffs[i], x, res); - mul(-m_coeffs[i], y, res); - is_strict = true; - is_eq = false; - } - if (a.is_le(c, x, y) || a.is_ge(c, y, x)) { - mul(m_coeffs[i], x, res); - mul(-m_coeffs[i], y, res); - is_eq = false; - } - } - - zero = a.mk_numeral(rational::zero(), a.is_int(res)); - if (is_eq) { - res = m.mk_eq(res, zero); - } - else if (is_strict) { - res = mk_lt(res, zero); - } - else { - res = mk_le(res, zero); - } - res = m.mk_not(res); - th_rewriter rw(m); - params_ref params; - params.set_bool("gcd_rounding", true); - rw.updt_params(params); - proof_ref pr(m); - expr_ref result(m); - rw(res, result, pr); - fix_dl(result); - return result; - } - - // patch: swap addends to make static - // features recognize difference constraint. - void fix_dl(expr_ref& r) { - expr* e; - if (m.is_not(r, e)) { - r = e; - fix_dl(r); - r = m.mk_not(r); - return; - } - expr* e1, *e2, *e3, *e4; - if ((m.is_eq(r, e1, e2) || a.is_lt(r, e1, e2) || a.is_gt(r, e1, e2) || - a.is_le(r, e1, e2) || a.is_ge(r, e1, e2))) { - if (a.is_add(e1, e3, e4) && a.is_mul(e3)) { - r = m.mk_app(to_app(r)->get_decl(), a.mk_add(e4,e3), e2); - } - } - } - }; - - farkas_learner::farkas_learner(smt_params& params, ast_manager& outer_mgr) - : m_proof_params(get_proof_params(params)), - m_pr(PGM_ENABLED), - m_constr(nullptr), - m_combine_farkas_coefficients(true), - p2o(m_pr, outer_mgr), - o2p(outer_mgr, m_pr) - { - reg_decl_plugins(m_pr); - m_ctx = alloc(smt::kernel, m_pr, m_proof_params); - } - - farkas_learner::~farkas_learner() { - dealloc(m_constr); - } - - smt_params farkas_learner::get_proof_params(smt_params& orig_params) { - smt_params res(orig_params); - res.m_arith_bound_prop = BP_NONE; - // temp hack to fix the build - // res.m_conflict_resolution_strategy = CR_ALL_DECIDED; - res.m_arith_auto_config_simplex = true; - res.m_arith_propagate_eqs = false; - res.m_arith_eager_eq_axioms = false; - res.m_arith_eq_bounds = false; - return res; - } - - class farkas_learner::equality_expander_cfg : public default_rewriter_cfg - { - ast_manager& m; - arith_util m_ar; - public: - equality_expander_cfg(ast_manager& m) : m(m), m_ar(m) {} - - bool get_subst(expr * s, expr * & t, proof * & t_pr) { - expr * x, *y; - if (m.is_eq(s, x, y) && (m_ar.is_int(x) || m_ar.is_real(x))) { - t = m.mk_and(m_ar.mk_ge(x, y), m_ar.mk_le(x, y)); - return true; - } - else { - return false; - } - } - - }; - - class collect_pure_proc { - func_decl_set& m_symbs; - public: - collect_pure_proc(func_decl_set& s):m_symbs(s) {} - - void operator()(app* a) { - if (a->get_family_id() == null_family_id) { - m_symbs.insert(a->get_decl()); - } - } - void operator()(var*) {} - void operator()(quantifier*) {} - }; - - - bool farkas_learner::get_lemma_guesses(expr * A_ext, expr * B_ext, expr_ref_vector& lemmas) - { - expr_ref A(o2p(A_ext), m_pr); - expr_ref B(o2p(B_ext), m_pr); - proof_ref pr(m_pr); - expr_ref tmp(m_pr); - expr_ref_vector ilemmas(m_pr); - equality_expander_cfg ee_rwr_cfg(m_pr); - rewriter_tpl ee_rwr(m_pr, false, ee_rwr_cfg); - - lemmas.reset(); - - ee_rwr(A, A); - ee_rwr(B, B); - - expr_set bs; - expr_ref_vector blist(m_pr); - flatten_and(B, blist); - for (unsigned i = 0; i < blist.size(); ++i) { - bs.insert(blist[i].get()); - } - - - if (!m_ctx) { - m_ctx = alloc(smt::kernel, m_pr, m_proof_params); - } - - m_ctx->push(); - m_ctx->assert_expr(A); - expr_set::iterator it = bs.begin(), end = bs.end(); - for (; it != end; ++it) { - m_ctx->assert_expr(*it); - } - lbool res = m_ctx->check(); - bool is_unsat = res == l_false; - if (is_unsat) { - pr = m_ctx->get_proof(); - get_lemmas(m_ctx->get_proof(), bs, ilemmas); - for (unsigned i = 0; i < ilemmas.size(); ++i) { - lemmas.push_back(p2o(ilemmas[i].get())); - } - } - m_ctx->pop(1); - - IF_VERBOSE(3, { - for (unsigned i = 0; i < ilemmas.size(); ++i) { - verbose_stream() << "B': " << mk_pp(ilemmas[i].get(), m_pr) << "\n"; - } - }); - - TRACE("farkas_learner", - tout << (is_unsat?"unsat":"sat") << "\n"; - tout << "A: " << mk_pp(A_ext, m_ctx->m()) << "\n"; - tout << "B: " << mk_pp(B_ext, m_ctx->m()) << "\n"; - for (unsigned i = 0; i < lemmas.size(); ++i) { - tout << "B': " << mk_pp(ilemmas[i].get(), m_pr) << "\n"; - }); - DEBUG_CODE( - if (is_unsat) { - m_ctx->push(); - m_ctx->assert_expr(A); - for (unsigned i = 0; i < ilemmas.size(); ++i) { - m_ctx->assert_expr(ilemmas[i].get()); - } - lbool res2 = m_ctx->check(); - SASSERT(l_false == res2); - m_ctx->pop(1); - } - ); - return is_unsat; - } - - // - // Perform simple subsumption check of lemmas. - // - void farkas_learner::simplify_lemmas(expr_ref_vector& lemmas) { - ast_manager& m = lemmas.get_manager(); - goal_ref g(alloc(goal, m, false, false, false)); - TRACE("farkas_simplify_lemmas", - for (unsigned i = 0; i < lemmas.size(); ++i) { - tout << mk_pp(lemmas[i].get(), m) << "\n"; - }); - - for (unsigned i = 0; i < lemmas.size(); ++i) { - g->assert_expr(lemmas[i].get()); - } - expr_ref tmp(m); - goal_ref_buffer result; - tactic_ref simplifier = mk_arith_bounds_tactic(m); - (*simplifier)(g, result); - lemmas.reset(); - SASSERT(result.size() == 1); - goal* r = result[0]; - for (unsigned i = 0; i < r->size(); ++i) { - lemmas.push_back(r->form(i)); - } - TRACE("farkas_simplify_lemmas", - tout << "simplified:\n"; - for (unsigned i = 0; i < lemmas.size(); ++i) { - tout << mk_pp(lemmas[i].get(), m) << "\n"; - }); - } - - - void farkas_learner::combine_constraints(unsigned n, app * const * lits, rational const * coeffs, expr_ref& res) - { - ast_manager& m = res.get_manager(); - if (m_combine_farkas_coefficients) { - if (!m_constr) { - m_constr = alloc(constr, m); - } - m_constr->reset(); - for (unsigned i = 0; i < n; ++i) { - m_constr->add(coeffs[i], lits[i]); - } - m_constr->get(res); - } - else { - bool_rewriter rw(m); - rw.mk_or(n, (expr*const*)(lits), res); - res = m.mk_not(res); - } - } - - class farkas_learner::constant_replacer_cfg : public default_rewriter_cfg - { - const obj_map& m_translation; - public: - constant_replacer_cfg(const obj_map& translation) - : m_translation(translation) - { } - - bool get_subst(expr * s, expr * & t, proof * & t_pr) { - return m_translation.find(s, t); - } - }; - - // every uninterpreted symbol is in symbs - class is_pure_expr_proc { - func_decl_set const& m_symbs; - public: - struct non_pure {}; - - is_pure_expr_proc(func_decl_set const& s):m_symbs(s) {} - - void operator()(app* a) { - if (a->get_family_id() == null_family_id) { - if (!m_symbs.contains(a->get_decl())) { - throw non_pure(); - } - } - } - void operator()(var*) {} - void operator()(quantifier*) {} - }; - - bool farkas_learner::is_pure_expr(func_decl_set const& symbs, expr* e) const { - is_pure_expr_proc proc(symbs); - try { - for_each_expr(proc, e); - } - catch (is_pure_expr_proc::non_pure) { - return false; - } - return true; - }; - - - /** - Revised version of Farkas strengthener. - 1. Mark B-pure nodes as derivations that depend only on B. - 2. Collect B-influenced nodes - 3. (optional) Permute B-pure units over resolution steps to narrow dependencies on B. - 4. Weaken B-pure units for resolution with Farkas Clauses. - 5. Add B-pure units elsewhere. - - Rules: - - hypothesis h |- h - - H |- false - - lemma ---------- - |- not H - - Th |- L \/ C H |- not L - - th-lemma ------------------------- - H |- C - - Note: C is false for theory axioms, C is unit literal for propagation. - - - rewrite |- t = s - - H |- t = s - - monotonicity ---------------- - H |- f(t) = f(s) - - H |- t = s H' |- s = u - - trans ---------------------- - H, H' |- t = u - - H |- C \/ L H' |- not L - - unit_resolve ------------------------ - H, H' |- C - - H |- a ~ b H' |- a - - mp -------------------- - H, H' |- b - - - def-axiom |- C - - - asserted |- f - - Mark nodes by: - - Hypotheses - - Dependency on bs - - Dependency on A - - A node is unit derivable from bs if: - - It has no hypotheses. - - It depends on bs. - - It does not depend on A. - - NB: currently unit derivable is not symmetric: A clause can be - unit derivable, but a unit literal with hypotheses is not. - This is clearly wrong, because hypotheses are just additional literals - in a clausal version. - - NB: the routine is not interpolating, though an interpolating variant would - be preferrable because then we can also use it for model propagation. - - We collect the unit derivable nodes from bs. - These are the weakenings of bs, besides the - units under Farkas. - - */ - -#define INSERT(_x_) if (!lemma_set.contains(_x_)) { lemma_set.insert(_x_); lemmas.push_back(_x_); } - - void farkas_learner::get_lemmas(proof* root, expr_set const& bs, expr_ref_vector& lemmas) { - ast_manager& m = lemmas.get_manager(); - bool_rewriter brwr(m); - func_decl_set Bsymbs; - collect_pure_proc collect_proc(Bsymbs); - expr_set::iterator it = bs.begin(), end = bs.end(); - for (; it != end; ++it) { - for_each_expr(collect_proc, *it); - } - - proof_ref pr(root, m); - proof_utils::reduce_hypotheses(pr); - proof_utils::permute_unit_resolution(pr); - IF_VERBOSE(3, verbose_stream() << "Reduced proof:\n" << mk_ismt2_pp(pr, m) << "\n";); - - ptr_vector hyprefs; - obj_map hypmap; - obj_hashtable lemma_set; - ast_mark b_depend, a_depend, visited, b_closed; - expr_set* empty_set = alloc(expr_set); - hyprefs.push_back(empty_set); - ptr_vector todo; - TRACE("pdr_verbose", tout << mk_pp(pr, m) << "\n";); - todo.push_back(pr); - while (!todo.empty()) { - proof* p = todo.back(); - SASSERT(m.is_proof(p)); - if (visited.is_marked(p)) { - todo.pop_back(); - continue; - } - bool all_visit = true; - for (unsigned i = 0; i < m.get_num_parents(p); ++i) { - expr* arg = p->get_arg(i); - SASSERT(m.is_proof(arg)); - if (!visited.is_marked(arg)) { - all_visit = false; - todo.push_back(to_app(arg)); - } - } - if (!all_visit) { - continue; - } - visited.mark(p, true); - todo.pop_back(); - - // retrieve hypotheses and dependencies on A, bs. - bool b_dep = false, a_dep = false; - expr_set* hyps = empty_set; - for (unsigned i = 0; i < m.get_num_parents(p); ++i) { - expr* arg = p->get_arg(i); - a_dep = a_dep || a_depend.is_marked(arg); - b_dep = b_dep || b_depend.is_marked(arg); - expr_set* hyps2 = hypmap.find(arg); - if (hyps != hyps2 && !hyps2->empty()) { - if (hyps->empty()) { - hyps = hyps2; - } - else { - expr_set* hyps3 = alloc(expr_set); - set_union(*hyps3, *hyps); - set_union(*hyps3, *hyps2); - hyps = hyps3; - hyprefs.push_back(hyps); - } - } - } - hypmap.insert(p, hyps); - a_depend.mark(p, a_dep); - b_depend.mark(p, b_dep); - -#define IS_B_PURE(_p) (b_depend.is_marked(_p) && !a_depend.is_marked(_p) && hypmap.find(_p)->empty()) - - - // Add lemmas that depend on bs, have no hypotheses, don't depend on A. - if ((!hyps->empty() || a_depend.is_marked(p)) && - b_depend.is_marked(p) && !is_farkas_lemma(m, p)) { - for (unsigned i = 0; i < m.get_num_parents(p); ++i) { - app* arg = to_app(p->get_arg(i)); - if (IS_B_PURE(arg)) { - expr* fact = m.get_fact(arg); - if (is_pure_expr(Bsymbs, fact)) { - TRACE("farkas_learner", - tout << "Add: " << mk_pp(m.get_fact(arg), m) << "\n"; - tout << mk_pp(arg, m) << "\n"; - ); - INSERT(fact); - } - else { - get_asserted(p, bs, b_closed, lemma_set, lemmas); - b_closed.mark(p, true); - } - } - } - } - - switch(p->get_decl_kind()) { - case PR_ASSERTED: - if (bs.contains(m.get_fact(p))) { - b_depend.mark(p, true); - } - else { - a_depend.mark(p, true); - } - break; - case PR_HYPOTHESIS: { - SASSERT(hyps == empty_set); - hyps = alloc(expr_set); - hyps->insert(m.get_fact(p)); - hyprefs.push_back(hyps); - hypmap.insert(p, hyps); - break; - } - case PR_DEF_AXIOM: { - if (!is_pure_expr(Bsymbs, m.get_fact(p))) { - a_depend.mark(p, true); - } - break; - } - case PR_LEMMA: { - expr_set* hyps2 = alloc(expr_set); - hyprefs.push_back(hyps2); - set_union(*hyps2, *hyps); - hyps = hyps2; - expr* fml = m.get_fact(p); - hyps->remove(fml); - if (m.is_or(fml)) { - for (unsigned i = 0; i < to_app(fml)->get_num_args(); ++i) { - expr* f = to_app(fml)->get_arg(i); - expr_ref hyp(m); - brwr.mk_not(f, hyp); - hyps->remove(hyp); - } - } - hypmap.insert(p, hyps); - break; - } - case PR_TH_LEMMA: { - if (!is_farkas_lemma(m, p)) break; - - SASSERT(m.has_fact(p)); - unsigned prem_cnt = m.get_num_parents(p); - func_decl * d = p->get_decl(); - SASSERT(d->get_num_parameters() >= prem_cnt+2); - SASSERT(d->get_parameter(0).get_symbol() == "arith"); - SASSERT(d->get_parameter(1).get_symbol() == "farkas"); - parameter const* params = d->get_parameters() + 2; - - app_ref_vector lits(m); - expr_ref tmp(m); - unsigned num_b_pures = 0; - rational coef; - vector coeffs; - - TRACE("farkas_learner", - for (unsigned i = 0; i < prem_cnt; ++i) { - VERIFY(params[i].is_rational(coef)); - proof* prem = to_app(p->get_arg(i)); - bool b_pure = IS_B_PURE(prem); - tout << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m.get_fact(prem), m) << "\n"; - } - tout << mk_pp(m.get_fact(p), m) << "\n"; - ); - - // NB. Taking 'abs' of coefficients is a workaround. - // The Farkas coefficient extraction in arith_core must be wrong. - // The coefficients would be always positive relative to the theory lemma. - - for(unsigned i = 0; i < prem_cnt; ++i) { - expr * prem_e = p->get_arg(i); - SASSERT(is_app(prem_e)); - proof * prem = to_app(prem_e); - - if(IS_B_PURE(prem)) { - ++num_b_pures; - } - else { - VERIFY(params[i].is_rational(coef)); - lits.push_back(to_app(m.get_fact(prem))); - coeffs.push_back(abs(coef)); - } - } - params += prem_cnt; - if (prem_cnt + 2 < d->get_num_parameters()) { - unsigned num_args = 1; - expr* fact = m.get_fact(p); - expr* const* args = &fact; - if (m.is_or(fact)) { - app* _or = to_app(fact); - num_args = _or->get_num_args(); - args = _or->get_args(); - } - SASSERT(prem_cnt + 2 + num_args == d->get_num_parameters()); - for (unsigned i = 0; i < num_args; ++i) { - expr* prem_e = args[i]; - brwr.mk_not(prem_e, tmp); - VERIFY(params[i].is_rational(coef)); - SASSERT(is_app(tmp)); - lits.push_back(to_app(tmp)); - coeffs.push_back(abs(coef)); - } - - } - SASSERT(coeffs.size() == lits.size()); - if (num_b_pures > 0) { - expr_ref res(m); - combine_constraints(coeffs.size(), lits.c_ptr(), coeffs.c_ptr(), res); - TRACE("farkas_learner", tout << "Add: " << mk_pp(res, m) << "\n";); - INSERT(res); - b_closed.mark(p, true); - } - } - default: - break; - } - } - - std::for_each(hyprefs.begin(), hyprefs.end(), delete_proc()); - simplify_lemmas(lemmas); - } - - void farkas_learner::get_consequences(proof* root, expr_set const& bs, expr_ref_vector& consequences) { - TRACE("farkas_learner", tout << "get consequences\n";); - m_combine_farkas_coefficients = false; - get_lemmas(root, bs, consequences); - m_combine_farkas_coefficients = true; - } - - void farkas_learner::get_asserted(proof* p, expr_set const& bs, ast_mark& b_closed, obj_hashtable& lemma_set, expr_ref_vector& lemmas) { - ast_manager& m = lemmas.get_manager(); - ast_mark visited; - proof* p0 = p; - ptr_vector todo; - todo.push_back(p); - - while (!todo.empty()) { - p = todo.back(); - todo.pop_back(); - if (visited.is_marked(p) || b_closed.is_marked(p)) { - continue; - } - visited.mark(p, true); - for (unsigned i = 0; i < m.get_num_parents(p); ++i) { - expr* arg = p->get_arg(i); - SASSERT(m.is_proof(arg)); - todo.push_back(to_app(arg)); - } - if (p->get_decl_kind() == PR_ASSERTED && - bs.contains(m.get_fact(p))) { - expr* fact = m.get_fact(p); - (void)p0; - TRACE("farkas_learner", - tout << mk_ll_pp(p0,m) << "\n"; - tout << "Add: " << mk_pp(p,m) << "\n";); - INSERT(fact); - b_closed.mark(p, true); - } - } - } - - - bool farkas_learner::is_farkas_lemma(ast_manager& m, expr* e) { - app * a; - func_decl* d; - symbol sym; - return - is_app(e) && - (a = to_app(e), d = a->get_decl(), true) && - PR_TH_LEMMA == a->get_decl_kind() && - d->get_num_parameters() >= 2 && - d->get_parameter(0).is_symbol(sym) && sym == "arith" && - d->get_parameter(1).is_symbol(sym) && sym == "farkas" && - d->get_num_parameters() >= m.get_num_parents(to_app(e)) + 2; - }; - - - void farkas_learner::test() { - smt_params params; - enable_trace("farkas_learner"); - - bool res; - ast_manager m; - reg_decl_plugins(m); - arith_util a(m); - pdr::farkas_learner fl(params, m); - expr_ref_vector lemmas(m); - - sort_ref int_s(a.mk_int(), m); - expr_ref x(m.mk_const(symbol("x"), int_s), m); - expr_ref y(m.mk_const(symbol("y"), int_s), m); - expr_ref z(m.mk_const(symbol("z"), int_s), m); - expr_ref u(m.mk_const(symbol("u"), int_s), m); - expr_ref v(m.mk_const(symbol("v"), int_s), m); - - // A: x > y >= z - // B: x < z - // Farkas: x <= z - expr_ref A(m.mk_and(a.mk_gt(x,y), a.mk_ge(y,z)),m); - expr_ref B(a.mk_gt(z,x),m); - res = fl.get_lemma_guesses(A, B, lemmas); - std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; - - // A: x > y >= z + 2 - // B: x = 1, z = 8 - // Farkas: x <= z + 2 - expr_ref one(a.mk_numeral(rational(1), true), m); - expr_ref two(a.mk_numeral(rational(2), true), m); - expr_ref eight(a.mk_numeral(rational(8), true), m); - A = m.mk_and(a.mk_gt(x,y),a.mk_ge(y,a.mk_add(z,two))); - B = m.mk_and(m.mk_eq(x,one), m.mk_eq(z, eight)); - res = fl.get_lemma_guesses(A, B, lemmas); - std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; - - // A: x > y >= z \/ x >= u > z - // B: z > x + 1 - // Farkas: z >= x - A = m.mk_or(m.mk_and(a.mk_gt(x,y),a.mk_ge(y,z)),m.mk_and(a.mk_ge(x,u),a.mk_gt(u,z))); - B = a.mk_gt(z, a.mk_add(x,one)); - res = fl.get_lemma_guesses(A, B, lemmas); - std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; - - // A: (x > y >= z \/ x >= u > z \/ u > v) - // B: z > x + 1 & not (u > v) - // Farkas: z >= x & not (u > v) - A = m.mk_or(m.mk_and(a.mk_gt(x,y),a.mk_ge(y,z)),m.mk_and(a.mk_ge(x,u),a.mk_gt(u,z)), a.mk_gt(u, v)); - B = m.mk_and(a.mk_gt(z, a.mk_add(x,one)), m.mk_not(a.mk_gt(u, v))); - res = fl.get_lemma_guesses(A, B, lemmas); - std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; - - } - - void farkas_learner::collect_statistics(statistics& st) const { - if (m_ctx) { - m_ctx->collect_statistics(st); - } - } - - -}; - diff --git a/src/muz/pdr/pdr_farkas_learner.h b/src/muz/pdr/pdr_farkas_learner.h deleted file mode 100644 index a5f3482e6..000000000 --- a/src/muz/pdr/pdr_farkas_learner.h +++ /dev/null @@ -1,128 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_farkas_learner.h - -Abstract: - - SMT2 interface for the datalog PDR - -Author: - - Krystof Hoder (t-khoder) 2011-11-1. - -Revision History: - ---*/ - -#ifndef PDR_FARKAS_LEARNER_H_ -#define PDR_FARKAS_LEARNER_H_ - -#include "ast/arith_decl_plugin.h" -#include "ast/ast_translation.h" -#include "ast/bv_decl_plugin.h" -#include "smt/smt_kernel.h" -#include "ast/rewriter/bool_rewriter.h" -#include "muz/pdr/pdr_util.h" -#include "smt/params/smt_params.h" -#include "tactic/tactic.h" - -namespace pdr { - -class farkas_learner { - class farkas_collector; - class constant_replacer_cfg; - class equality_expander_cfg; - class constr; - - typedef obj_hashtable expr_set; - - smt_params m_proof_params; - ast_manager m_pr; - scoped_ptr m_ctx; - constr* m_constr; - - // - // true: produce a combined constraint by applying Farkas coefficients. - // false: produce a conjunction of the negated literals from the theory lemmas. - // - bool m_combine_farkas_coefficients; - - - static smt_params get_proof_params(smt_params& orig_params); - - // - // all ast objects passed to private functions have m_proof_mgs as their ast_manager - // - - ast_translation p2o; /** Translate expression from inner ast_manager to outer one */ - ast_translation o2p; /** Translate expression from outer ast_manager to inner one */ - - - /** All ast opbjects here are in the m_proof_mgs */ - void get_lemma_guesses_internal(proof * p, expr* A, expr * B, expr_ref_vector& lemmas); - - bool farkas2lemma(proof * fstep, expr* A, expr * B, expr_ref& res); - - void combine_constraints(unsigned cnt, app * const * constrs, rational const * coeffs, expr_ref& res); - - bool try_ensure_lemma_in_language(expr_ref& lemma, expr* A, const func_decl_set& lang); - - bool is_farkas_lemma(ast_manager& m, expr* e); - - void get_asserted(proof* p, expr_set const& bs, ast_mark& b_closed, obj_hashtable& lemma_set, expr_ref_vector& lemmas); - - bool is_pure_expr(func_decl_set const& symbs, expr* e) const; - - static void test(); - -public: - farkas_learner(smt_params& params, ast_manager& m); - - ~farkas_learner(); - - /** - All ast objects have the ast_manager which was passed as - an argument to the constructor (i.e. m_outer_mgr) - - B is a conjunction of literals. - A && B is unsat, equivalently A => ~B is valid - Find a weakened B' such that - A && B' is unsat and B' uses vocabulary (and constants) in common with A. - return lemmas to weaken B. - */ - - bool get_lemma_guesses(expr * A, expr * B, expr_ref_vector& lemmas); - - /** - Traverse a proof and retrieve lemmas using the vocabulary from bs. - */ - void get_lemmas(proof* root, expr_set const& bs, expr_ref_vector& lemmas); - - /** - Traverse a proof and retrieve consequences of A that are used to establish ~B. - The assumption is that: - - A => \/ ~consequences[i] and \/ ~consequences[i] => ~B - - e.g., the second implication can be rewritten as: - - B => /\ consequences[i] - */ - void get_consequences(proof* root, expr_set const& bs, expr_ref_vector& consequences); - - /** - \brief Simplify lemmas using subsumption. - */ - void simplify_lemmas(expr_ref_vector& lemmas); - - void collect_statistics(statistics& st) const; - -}; - - -} - -#endif diff --git a/src/muz/pdr/pdr_generalizers.cpp b/src/muz/pdr/pdr_generalizers.cpp deleted file mode 100644 index 8e52cb6f4..000000000 --- a/src/muz/pdr/pdr_generalizers.cpp +++ /dev/null @@ -1,777 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_generalizers.cpp - -Abstract: - - Generalizers of satisfiable states and unsat cores. - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-20. - -Revision History: - ---*/ - - -#include "muz/pdr/pdr_context.h" -#include "muz/pdr/pdr_farkas_learner.h" -#include "muz/pdr/pdr_generalizers.h" -#include "ast/expr_abstract.h" -#include "ast/rewriter/var_subst.h" -#include "ast/rewriter/expr_safe_replace.h" -#include "model/model_smt2_pp.h" - - -namespace pdr { - - - // ------------------------ - // core_bool_inductive_generalizer - - // main propositional induction generalizer. - // drop literals one by one from the core and check if the core is still inductive. - // - void core_bool_inductive_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { - if (core.size() <= 1) { - return; - } - ast_manager& m = core.get_manager(); - TRACE("pdr", for (unsigned i = 0; i < core.size(); ++i) { tout << mk_pp(core[i].get(), m) << "\n"; }); - unsigned num_failures = 0, i = 0, old_core_size = core.size(); - ptr_vector processed; - - while (i < core.size() && 1 < core.size() && (!m_failure_limit || num_failures <= m_failure_limit)) { - expr_ref lit(m); - lit = core[i].get(); - core[i] = m.mk_true(); - if (n.pt().check_inductive(n.level(), core, uses_level)) { - num_failures = 0; - for (i = 0; i < core.size() && processed.contains(core[i].get()); ++i); - } - else { - core[i] = lit; - processed.push_back(lit); - ++num_failures; - ++i; - } - } - IF_VERBOSE(2, verbose_stream() << "old size: " << old_core_size << " new size: " << core.size() << "\n";); - TRACE("pdr", tout << "old size: " << old_core_size << " new size: " << core.size() << "\n";); - } - - - void core_multi_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { - UNREACHABLE(); - } - - /** - \brief Find minimal cores. - Apply a simple heuristic: find a minimal core, then find minimal cores that exclude at least one - literal from each of the literals in the minimal cores. - */ - void core_multi_generalizer::operator()(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) { - ast_manager& m = core.get_manager(); - expr_ref_vector old_core(m), core0(core); - bool uses_level1 = uses_level; - m_gen(n, core0, uses_level1); - new_cores.push_back(std::make_pair(core0, uses_level1)); - obj_hashtable core_exprs, core1_exprs; - set_union(core_exprs, core0); - for (unsigned i = 0; i < old_core.size(); ++i) { - expr* lit = old_core[i].get(); - if (core_exprs.contains(lit)) { - expr_ref_vector core1(old_core); - core1[i] = core1.back(); - core1.pop_back(); - uses_level1 = uses_level; - m_gen(n, core1, uses_level1); - SASSERT(core1.size() <= old_core.size()); - if (core1.size() < old_core.size()) { - new_cores.push_back(std::make_pair(core1, uses_level1)); - core1_exprs.reset(); - set_union(core1_exprs, core1); - set_intersection(core_exprs, core1_exprs); - } - } - } - } - - // ------------------------ - // core_farkas_generalizer - - // - // for each disjunct of core: - // weaken predecessor. - // - - core_farkas_generalizer::core_farkas_generalizer(context& ctx, ast_manager& m, smt_params& p): - core_generalizer(ctx), - m_farkas_learner(p, m) - {} - - void core_farkas_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { - ast_manager& m = n.pt().get_manager(); - if (core.empty()) return; - expr_ref A(m), B(mk_and(core)), C(m); - expr_ref_vector Bs(m); - flatten_or(B, Bs); - A = n.pt().get_propagation_formula(m_ctx.get_pred_transformers(), n.level()); - - bool change = false; - for (unsigned i = 0; i < Bs.size(); ++i) { - expr_ref_vector lemmas(m); - C = Bs[i].get(); - if (m_farkas_learner.get_lemma_guesses(A, B, lemmas)) { - TRACE("pdr", - tout << "Old core:\n" << mk_pp(B, m) << "\n"; - tout << "New core:\n" << mk_and(lemmas) << "\n";); - Bs[i] = mk_and(lemmas); - change = true; - } - } - if (change) { - C = mk_or(Bs); - TRACE("pdr", tout << "prop:\n" << mk_pp(A,m) << "\ngen:" << mk_pp(B, m) << "\nto: " << mk_pp(C, m) << "\n";); - core.reset(); - flatten_and(C, core); - uses_level = true; - } - } - - void core_farkas_generalizer::collect_statistics(statistics& st) const { - m_farkas_learner.collect_statistics(st); - } - - - core_convex_hull_generalizer::core_convex_hull_generalizer(context& ctx, bool is_closure): - core_generalizer(ctx), - m(ctx.get_manager()), - m_is_closure(is_closure) { - } - - void core_convex_hull_generalizer::operator()(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) { - // method3(n, core, uses_level, new_cores); - method1(n, core, uses_level, new_cores); - } - - void core_convex_hull_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { - UNREACHABLE(); - } - - // use the entire region as starting point for generalization. - // - // Constraints: - // add_variables: y = y1 + y2 - // core: Ay <= b -> conv1: A*y1 <= b*sigma1 - // sigma1 > 0 - // sigma2 > 0 - // 1 = sigma1 + sigma2 - // A'y <= b' -> conv2: A'*y2 <= b'*sigma2 - // - // If Constraints & Transition(y0, y) is unsat, then - // update with new core. - // - void core_convex_hull_generalizer::method1(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) { - expr_ref_vector conv2(m), fmls(m), fml1_2(m); - bool change = false; - - if (core.empty()) { - new_cores.push_back(std::make_pair(core, uses_level)); - return; - } - closure cl(n.pt(), m_is_closure); - - expr_ref fml1 = mk_and(core); - expr_ref fml2 = n.pt().get_formulas(n.level(), false); - fml1_2.push_back(fml1); - fml1_2.push_back(nullptr); - flatten_and(fml2, fmls); - for (unsigned i = 0; i < fmls.size(); ++i) { - fml2 = m.mk_not(fmls[i].get()); - fml1_2[1] = fml2; - expr_ref state = cl(fml1_2); - TRACE("pdr", - tout << "Check states:\n" << mk_pp(state, m) << "\n"; - tout << "Old states:\n" << mk_pp(fml2, m) << "\n"; - ); - model_node nd(nullptr, state, n.pt(), n.level()); - pred_transformer::scoped_farkas sf(n.pt(), true); - bool uses_level1 = uses_level; - if (l_false == n.pt().is_reachable(nd, &conv2, uses_level1)) { - new_cores.push_back(std::make_pair(conv2, uses_level1)); - change = true; - expr_ref state1 = mk_and(conv2); - TRACE("pdr", - tout << mk_pp(state, m) << "\n"; - tout << "Generalized to:\n" << mk_pp(state1, m) << "\n";); - IF_VERBOSE(0, - verbose_stream() << mk_pp(state, m) << "\n"; - verbose_stream() << "Generalized to:\n" << mk_pp(state1, m) << "\n";); - } - } - if (!m_is_closure || !change) { - new_cores.push_back(std::make_pair(core, uses_level)); - } - } - - /* - Extract the lemmas from the transition relation that were used to establish unsatisfiability. - Take convex closures of conbinations of these lemmas. - */ - void core_convex_hull_generalizer::method3(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) { - TRACE("dl", tout << "method: generalize consequences of F(R)\n"; - for (unsigned i = 0; i < core.size(); ++i) { - tout << "B:" << mk_pp(core[i], m) << "\n"; - }); - bool uses_level1; - expr_ref_vector core1(m); - core1.append(core); - expr_ref_vector consequences(m); - { - n.pt().get_solver().set_consequences(&consequences); - pred_transformer::scoped_farkas sf (n.pt(), true); - VERIFY(l_false == n.pt().is_reachable(n, &core1, uses_level1)); - n.pt().get_solver().set_consequences(nullptr); - } - IF_VERBOSE(0, - verbose_stream() << "Consequences: " << consequences.size() << "\n"; - for (unsigned i = 0; i < consequences.size(); ++i) { - verbose_stream() << mk_pp(consequences[i].get(), m) << "\n"; - } - verbose_stream() << "core: " << core1.size() << "\n"; - for (unsigned i = 0; i < core1.size(); ++i) { - verbose_stream() << mk_pp(core1[i].get(), m) << "\n"; - }); - - expr_ref tmp(m); - - // Check that F(R) => \/ consequences - { - expr_ref_vector cstate(m); - for (unsigned i = 0; i < consequences.size(); ++i) { - cstate.push_back(m.mk_not(consequences[i].get())); - } - tmp = m.mk_and(cstate.size(), cstate.c_ptr()); - model_node nd(nullptr, tmp, n.pt(), n.level()); - pred_transformer::scoped_farkas sf (n.pt(), false); - VERIFY(l_false == n.pt().is_reachable(nd, &core1, uses_level1)); - } - - // Create disjunction. - tmp = m.mk_and(core.size(), core.c_ptr()); - - // Check that \/ consequences => not (core) - if (!is_unsat(consequences, tmp)) { - IF_VERBOSE(0, verbose_stream() << "Consequences don't contradict the core\n";); - return; - } - IF_VERBOSE(0, verbose_stream() << "Consequences contradict core\n";); - - if (!strengthen_consequences(n, consequences, tmp)) { - return; - } - - IF_VERBOSE(0, verbose_stream() << "consequences strengthened\n";); - // Use the resulting formula to find Farkas lemmas from core. - } - - bool core_convex_hull_generalizer::strengthen_consequences(model_node& n, expr_ref_vector& As, expr* B) { - expr_ref A(m), tmp(m), convA(m); - unsigned sz = As.size(); - closure cl(n.pt(), m_is_closure); - for (unsigned i = 0; i < As.size(); ++i) { - expr_ref_vector Hs(m); - Hs.push_back(As[i].get()); - for (unsigned j = i + 1; j < As.size(); ++j) { - Hs.push_back(As[j].get()); - bool unsat = false; - A = cl(Hs); - tmp = As[i].get(); - As[i] = A; - unsat = is_unsat(As, B); - As[i] = tmp; - if (unsat) { - IF_VERBOSE(0, verbose_stream() << "New convex: " << mk_pp(convA, m) << "\n";); - convA = A; - As[j] = As.back(); - As.pop_back(); - --j; - } - else { - Hs.pop_back(); - } - } - if (Hs.size() > 1) { - As[i] = convA; - } - } - return sz > As.size(); - } - - - bool core_convex_hull_generalizer::is_unsat(expr_ref_vector const& As, expr* B) { - smt::kernel ctx(m, m_ctx.get_fparams(), m_ctx.get_params().p); - expr_ref disj(m); - disj = m.mk_or(As.size(), As.c_ptr()); - ctx.assert_expr(disj); - ctx.assert_expr(B); - std::cout << "Checking\n" << mk_pp(disj, m) << "\n" << mk_pp(B, m) << "\n"; - return l_false == ctx.check(); - } - - - // --------------------------------- - // core_arith_inductive_generalizer - // NB. this is trying out some ideas for generalization in - // an ad hoc specialized way. arith_inductive_generalizer should - // not be used by default. It is a place-holder for a general purpose - // extrapolator of a lattice basis. - - core_arith_inductive_generalizer::core_arith_inductive_generalizer(context& ctx): - core_generalizer(ctx), - m(ctx.get_manager()), - a(m), - m_refs(m) {} - - void core_arith_inductive_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { - if (core.size() <= 1) { - return; - } - reset(); - expr_ref e(m), t1(m), t2(m), t3(m); - rational r; - - TRACE("pdr", for (unsigned i = 0; i < core.size(); ++i) { tout << mk_pp(core[i].get(), m) << "\n"; }); - - svector eqs; - get_eqs(core, eqs); - - if (eqs.empty()) { - return; - } - - expr_ref_vector new_core(m); - new_core.append(core); - - for (unsigned eq = 0; eq < eqs.size(); ++eq) { - rational r = eqs[eq].m_value; - expr* x = eqs[eq].m_term; - unsigned k = eqs[eq].m_i; - unsigned l = eqs[eq].m_j; - - new_core[l] = m.mk_true(); - new_core[k] = m.mk_true(); - - for (unsigned i = 0; i < new_core.size(); ++i) { - if (substitute_alias(r, x, new_core[i].get(), e)) { - new_core[i] = e; - } - } - if (abs(r) >= rational(2) && a.is_int(x)) { - new_core[k] = m.mk_eq(a.mk_mod(x, a.mk_numeral(rational(2), true)), a.mk_numeral(rational(0), true)); - new_core[l] = a.mk_le(x, a.mk_numeral(rational(0), true)); - } - } - - bool inductive = n.pt().check_inductive(n.level(), new_core, uses_level); - - IF_VERBOSE(1, - verbose_stream() << (inductive?"":"non") << "inductive\n"; - verbose_stream() << "old\n"; - for (unsigned j = 0; j < core.size(); ++j) { - verbose_stream() << mk_pp(core[j].get(), m) << "\n"; - } - verbose_stream() << "new\n"; - for (unsigned j = 0; j < new_core.size(); ++j) { - verbose_stream() << mk_pp(new_core[j].get(), m) << "\n"; - }); - - if (inductive) { - core.reset(); - core.append(new_core); - } - } - - void core_arith_inductive_generalizer::insert_bound(bool is_lower, expr* x, rational const& r, unsigned i) { - if (r.is_neg()) { - expr_ref e(m); - e = a.mk_uminus(x); - m_refs.push_back(e); - x = e; - is_lower = !is_lower; - } - - vector bound; - bound.push_back(std::make_pair(x, i)); - if (is_lower) { - m_lb.insert(abs(r), bound); - } - else { - m_ub.insert(abs(r), bound); - } - } - - void core_arith_inductive_generalizer::reset() { - m_refs.reset(); - m_lb.reset(); - m_ub.reset(); - } - - void core_arith_inductive_generalizer::get_eqs(expr_ref_vector const& core, svector& eqs) { - expr* e1, *x, *y; - expr_ref e(m); - rational r; - - for (unsigned i = 0; i < core.size(); ++i) { - e = core[i]; - if (m.is_not(e, e1) && a.is_le(e1, x, y) && a.is_numeral(y, r) && a.is_int(x)) { - // not (<= x r) <=> x >= r + 1 - insert_bound(true, x, r + rational(1), i); - } - else if (m.is_not(e, e1) && a.is_ge(e1, x, y) && a.is_numeral(y, r) && a.is_int(x)) { - // not (>= x r) <=> x <= r - 1 - insert_bound(false, x, r - rational(1), i); - } - else if (a.is_le(e, x, y) && a.is_numeral(y, r)) { - insert_bound(false, x, r, i); - } - else if (a.is_ge(e, x, y) && a.is_numeral(y, r)) { - insert_bound(true, x, r, i); - } - } - bounds_t::iterator it = m_lb.begin(), end = m_lb.end(); - for (; it != end; ++it) { - rational r = it->m_key; - vector & terms1 = it->m_value; - vector terms2; - if (r >= rational(2) && m_ub.find(r, terms2)) { - for (unsigned i = 0; i < terms1.size(); ++i) { - bool done = false; - for (unsigned j = 0; !done && j < terms2.size(); ++j) { - expr* t1 = terms1[i].first; - expr* t2 = terms2[j].first; - if (t1 == t2) { - eqs.push_back(eq(t1, r, terms1[i].second, terms2[j].second)); - done = true; - } - else { - e = m.mk_eq(t1, t2); - th_rewriter rw(m); - rw(e); - if (m.is_true(e)) { - eqs.push_back(eq(t1, r, terms1[i].second, terms2[j].second)); - done = true; - } - } - } - } - } - } - } - - bool core_arith_inductive_generalizer::substitute_alias(rational const& r, expr* x, expr* e, expr_ref& result) { - rational r2; - expr* y, *z, *e1; - if (m.is_not(e, e1) && substitute_alias(r, x, e1, result)) { - result = m.mk_not(result); - return true; - } - if (a.is_le(e, y, z) && a.is_numeral(z, r2)) { - if (r == r2) { - result = a.mk_le(y, x); - return true; - } - if (r == r2 + rational(1)) { - result = a.mk_lt(y, x); - return true; - } - if (r == r2 - rational(1)) { - result = a.mk_le(y, a.mk_sub(x, a.mk_numeral(rational(1), a.is_int(x)))); - return true; - } - - } - if (a.is_ge(e, y, z) && a.is_numeral(z, r2)) { - if (r == r2) { - result = a.mk_ge(y, x); - return true; - } - if (r2 == r + rational(1)) { - result = a.mk_gt(y, x); - return true; - } - if (r2 == r - rational(1)) { - result = a.mk_ge(y, a.mk_sub(x, a.mk_numeral(rational(1), a.is_int(x)))); - return true; - } - } - return false; - } - - - // - // < F, phi, i + 1> - // | - // < G, psi, i > - // - // where: - // - // p(x) <- F(x,y,p,q) - // q(x) <- G(x,y) - // - // Hyp: - // Q_k(x) => phi(x) j <= k <= i - // Q_k(x) => R_k(x) j <= k <= i + 1 - // Q_k(x) <=> Trans(Q_{k-1}) j < k <= i + 1 - // Conclusion: - // Q_{i+1}(x) => phi(x) - // - class core_induction_generalizer::imp { - context& m_ctx; - manager& pm; - ast_manager& m; - - // - // Create predicate Q_level - // - func_decl_ref mk_pred(unsigned level, func_decl* f) { - func_decl_ref result(m); - std::ostringstream name; - name << f->get_name() << "_" << level; - symbol sname(name.str().c_str()); - result = m.mk_func_decl(sname, f->get_arity(), f->get_domain(), f->get_range()); - return result; - } - - // - // Create formula exists y . z . F[Q_{level-1}, x, y, z] - // - expr_ref mk_transition_rule( - expr_ref_vector const& reps, - unsigned level, - datalog::rule const& rule) - { - expr_ref_vector conj(m), sub(m); - expr_ref result(m); - svector names; - unsigned ut_size = rule.get_uninterpreted_tail_size(); - unsigned t_size = rule.get_tail_size(); - if (0 == level && 0 < ut_size) { - result = m.mk_false(); - return result; - } - app* atom = rule.get_head(); - SASSERT(atom->get_num_args() == reps.size()); - - for (unsigned i = 0; i < reps.size(); ++i) { - expr* arg = atom->get_arg(i); - if (is_var(arg)) { - unsigned idx = to_var(arg)->get_idx(); - if (idx >= sub.size()) sub.resize(idx+1); - if (sub[idx].get()) { - conj.push_back(m.mk_eq(sub[idx].get(), reps[i])); - } - else { - sub[idx] = reps[i]; - } - } - else { - conj.push_back(m.mk_eq(arg, reps[i])); - } - } - for (unsigned i = 0; 0 < level && i < ut_size; i++) { - app* atom = rule.get_tail(i); - func_decl* head = atom->get_decl(); - func_decl_ref fn = mk_pred(level-1, head); - conj.push_back(m.mk_app(fn, atom->get_num_args(), atom->get_args())); - } - for (unsigned i = ut_size; i < t_size; i++) { - conj.push_back(rule.get_tail(i)); - } - result = mk_and(conj); - if (!sub.empty()) { - expr_ref tmp = result; - var_subst(m, false)(tmp, sub.size(), sub.c_ptr(), result); - } - expr_free_vars fv; - fv(result); - fv.set_default_sort(m.mk_bool_sort()); - for (unsigned i = 0; i < fv.size(); ++i) { - names.push_back(symbol(fv.size() - i - 1)); - } - if (!fv.empty()) { - fv.reverse(); - result = m.mk_exists(fv.size(), fv.c_ptr(), names.c_ptr(), result); - } - return result; - } - - expr_ref bind_head(expr_ref_vector const& reps, expr* fml) { - expr_ref result(m); - expr_abstract(m, 0, reps.size(), reps.c_ptr(), fml, result); - ptr_vector sorts; - svector names; - unsigned sz = reps.size(); - for (unsigned i = 0; i < sz; ++i) { - sorts.push_back(m.get_sort(reps[sz-i-1])); - names.push_back(symbol(sz-i-1)); - } - if (sz > 0) { - result = m.mk_forall(sorts.size(), sorts.c_ptr(), names.c_ptr(), result); - } - return result; - } - - expr_ref_vector mk_reps(pred_transformer& pt) { - expr_ref_vector reps(m); - expr_ref rep(m); - for (unsigned i = 0; i < pt.head()->get_arity(); ++i) { - rep = m.mk_const(pm.o2n(pt.sig(i), 0)); - reps.push_back(rep); - } - return reps; - } - - // - // extract transition axiom: - // - // forall x . p_lvl(x) <=> exists y z . F[p_{lvl-1}(y), q_{lvl-1}(z), x] - // - expr_ref mk_transition_axiom(pred_transformer& pt, unsigned level) { - expr_ref fml(m.mk_false(), m), tr(m); - expr_ref_vector reps = mk_reps(pt); - ptr_vector const& rules = pt.rules(); - for (unsigned i = 0; i < rules.size(); ++i) { - tr = mk_transition_rule(reps, level, *rules[i]); - fml = (i == 0)?tr.get():m.mk_or(fml, tr); - } - func_decl_ref fn = mk_pred(level, pt.head()); - fml = m.mk_iff(m.mk_app(fn, reps.size(), reps.c_ptr()), fml); - fml = bind_head(reps, fml); - return fml; - } - - // - // Create implication: - // Q_level(x) => phi(x) - // - expr_ref mk_predicate_property(unsigned level, pred_transformer& pt, expr* phi) { - expr_ref_vector reps = mk_reps(pt); - func_decl_ref fn = mk_pred(level, pt.head()); - expr_ref fml(m); - fml = m.mk_implies(m.mk_app(fn, reps.size(), reps.c_ptr()), phi); - fml = bind_head(reps, fml); - return fml; - } - - - - public: - imp(context& ctx): m_ctx(ctx), pm(ctx.get_pdr_manager()), m(ctx.get_manager()) {} - - // - // not exists y . F(x,y) - // - expr_ref mk_blocked_transition(pred_transformer& pt, unsigned level) { - SASSERT(level > 0); - expr_ref fml(m.mk_true(), m); - expr_ref_vector reps = mk_reps(pt), fmls(m); - ptr_vector const& rules = pt.rules(); - for (unsigned i = 0; i < rules.size(); ++i) { - fmls.push_back(m.mk_not(mk_transition_rule(reps, level, *rules[i]))); - } - fml = mk_and(fmls); - TRACE("pdr", tout << mk_pp(fml, m) << "\n";); - return fml; - } - - expr_ref mk_induction_goal(pred_transformer& pt, unsigned level, unsigned depth) { - SASSERT(level >= depth); - expr_ref_vector conjs(m); - ptr_vector pts; - unsigned_vector levels; - // negated goal - expr_ref phi = mk_blocked_transition(pt, level); - conjs.push_back(m.mk_not(mk_predicate_property(level, pt, phi))); - pts.push_back(&pt); - levels.push_back(level); - // Add I.H. - for (unsigned lvl = level-depth; lvl < level; ++lvl) { - if (lvl > 0) { - expr_ref psi = mk_blocked_transition(pt, lvl); - conjs.push_back(mk_predicate_property(lvl, pt, psi)); - pts.push_back(&pt); - levels.push_back(lvl); - } - } - // Transitions: - for (unsigned qhead = 0; qhead < pts.size(); ++qhead) { - pred_transformer& qt = *pts[qhead]; - unsigned lvl = levels[qhead]; - - // Add transition definition and properties at level. - conjs.push_back(mk_transition_axiom(qt, lvl)); - conjs.push_back(mk_predicate_property(lvl, qt, qt.get_formulas(lvl, true))); - - // Enqueue additional hypotheses - ptr_vector const& rules = qt.rules(); - if (lvl + depth < level || lvl == 0) { - continue; - } - for (unsigned i = 0; i < rules.size(); ++i) { - datalog::rule& r = *rules[i]; - unsigned ut_size = r.get_uninterpreted_tail_size(); - for (unsigned j = 0; j < ut_size; ++j) { - func_decl* f = r.get_tail(j)->get_decl(); - pred_transformer* rt = m_ctx.get_pred_transformers().find(f); - bool found = false; - for (unsigned k = 0; !found && k < levels.size(); ++k) { - found = (rt == pts[k] && levels[k] + 1 == lvl); - } - if (!found) { - levels.push_back(lvl-1); - pts.push_back(rt); - } - } - } - } - - expr_ref result = mk_and(conjs); - TRACE("pdr", tout << mk_pp(result, m) << "\n";); - return result; - } - }; - - // - // Instantiate Peano induction schema. - // - void core_induction_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { - model_node* p = n.parent(); - if (p == nullptr) { - return; - } - unsigned depth = 2; - imp imp(m_ctx); - ast_manager& m = core.get_manager(); - expr_ref goal = imp.mk_induction_goal(p->pt(), p->level(), depth); - smt::kernel ctx(m, m_ctx.get_fparams(), m_ctx.get_params().p); - ctx.assert_expr(goal); - lbool r = ctx.check(); - TRACE("pdr", tout << r << "\n"; - for (unsigned i = 0; i < core.size(); ++i) { - tout << mk_pp(core[i].get(), m) << "\n"; - }); - if (r == l_false) { - core.reset(); - expr_ref phi = imp.mk_blocked_transition(p->pt(), p->level()); - core.push_back(m.mk_not(phi)); - uses_level = true; - } - } -}; - diff --git a/src/muz/pdr/pdr_generalizers.h b/src/muz/pdr/pdr_generalizers.h deleted file mode 100644 index a75b347a4..000000000 --- a/src/muz/pdr/pdr_generalizers.h +++ /dev/null @@ -1,110 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_generalizers.h - -Abstract: - - Generalizer plugins. - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-22. - -Revision History: - ---*/ - -#ifndef PDR_GENERALIZERS_H_ -#define PDR_GENERALIZERS_H_ - -#include "muz/pdr/pdr_context.h" -#include "muz/pdr/pdr_closure.h" -#include "ast/arith_decl_plugin.h" - -namespace pdr { - - class core_bool_inductive_generalizer : public core_generalizer { - unsigned m_failure_limit; - public: - core_bool_inductive_generalizer(context& ctx, unsigned failure_limit) : core_generalizer(ctx), m_failure_limit(failure_limit) {} - ~core_bool_inductive_generalizer() override {} - void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) override; - }; - - template - class r_map : public map { - }; - - class core_arith_inductive_generalizer : public core_generalizer { - typedef std::pair term_loc_t; - typedef r_map > bounds_t; - - ast_manager& m; - arith_util a; - expr_ref_vector m_refs; - bounds_t m_lb; - bounds_t m_ub; - - struct eq { - expr* m_term; - rational m_value; - unsigned m_i; - unsigned m_j; - eq(expr* t, rational const& r, unsigned i, unsigned j): m_term(t), m_value(r), m_i(i), m_j(j) {} - }; - void reset(); - void insert_bound(bool is_lower, expr* x, rational const& r, unsigned i); - void get_eqs(expr_ref_vector const& core, svector& eqs); - bool substitute_alias(rational const&r, expr* x, expr* e, expr_ref& result); - public: - core_arith_inductive_generalizer(context& ctx); - ~core_arith_inductive_generalizer() override {} - void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) override; - }; - - class core_farkas_generalizer : public core_generalizer { - farkas_learner m_farkas_learner; - public: - core_farkas_generalizer(context& ctx, ast_manager& m, smt_params& p); - ~core_farkas_generalizer() override {} - void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) override; - void collect_statistics(statistics& st) const override; - }; - - - class core_convex_hull_generalizer : public core_generalizer { - ast_manager& m; - obj_map m_models; - bool m_is_closure; - void method1(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores); - void method3(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores); - bool strengthen_consequences(model_node& n, expr_ref_vector& As, expr* B); - bool is_unsat(expr_ref_vector const& As, expr* B); - public: - core_convex_hull_generalizer(context& ctx, bool is_closure); - ~core_convex_hull_generalizer() override {} - void operator()(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) override; - void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) override; - }; - - class core_multi_generalizer : public core_generalizer { - core_bool_inductive_generalizer m_gen; - public: - core_multi_generalizer(context& ctx, unsigned max_failures): core_generalizer(ctx), m_gen(ctx, max_failures) {} - ~core_multi_generalizer() override {} - void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) override; - void operator()(model_node& n, expr_ref_vector const& core, bool uses_level, cores& new_cores) override; - }; - - class core_induction_generalizer : public core_generalizer { - class imp; - public: - core_induction_generalizer(context& ctx): core_generalizer(ctx) {} - ~core_induction_generalizer() override {} - void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) override; - }; -}; -#endif diff --git a/src/muz/pdr/pdr_manager.cpp b/src/muz/pdr/pdr_manager.cpp deleted file mode 100644 index da15bf094..000000000 --- a/src/muz/pdr/pdr_manager.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_manager.cpp - -Abstract: - - A manager class for PDR, taking care of creating of AST - objects and conversions between them. - -Author: - - Krystof Hoder (t-khoder) 2011-8-25. - -Revision History: - ---*/ - -#include -#include "muz/pdr/pdr_manager.h" -#include "ast/ast_smt2_pp.h" -#include "ast/for_each_expr.h" -#include "ast/has_free_vars.h" -#include "ast/rewriter/expr_replacer.h" -#include "ast/expr_abstract.h" -#include "model/model2expr.h" -#include "model/model_smt2_pp.h" -#include "tactic/model_converter.h" - -namespace pdr { - - class collect_decls_proc { - func_decl_set& m_bound_decls; - func_decl_set& m_aux_decls; - public: - collect_decls_proc(func_decl_set& bound_decls, func_decl_set& aux_decls): - m_bound_decls(bound_decls), - m_aux_decls(aux_decls) { - } - - void operator()(app* a) { - if (a->get_family_id() == null_family_id) { - func_decl* f = a->get_decl(); - if (!m_bound_decls.contains(f)) { - m_aux_decls.insert(f); - } - } - } - void operator()(var* v) {} - void operator()(quantifier* q) {} - }; - - typedef hashtable symbol_set; - - expr_ref inductive_property::fixup_clause(expr* fml) const { - expr_ref_vector disjs(m); - flatten_or(fml, disjs); - expr_ref result(m); - bool_rewriter(m).mk_or(disjs.size(), disjs.c_ptr(), result); - return result; - } - - expr_ref inductive_property::fixup_clauses(expr* fml) const { - expr_ref_vector conjs(m); - expr_ref result(m); - flatten_and(fml, conjs); - for (unsigned i = 0; i < conjs.size(); ++i) { - conjs[i] = fixup_clause(conjs[i].get()); - } - bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), result); - return result; - } - - std::string inductive_property::to_string() const { - std::stringstream stm; - model_ref md; - expr_ref result(m); - to_model(md); - model_smt2_pp(stm, m, *md.get(), 0); - return stm.str(); - } - - void inductive_property::to_model(model_ref& md) const { - md = alloc(model, m); - vector const& rs = m_relation_info; - expr_ref_vector conjs(m); - for (unsigned i = 0; i < rs.size(); ++i) { - relation_info ri(rs[i]); - func_decl * pred = ri.m_pred; - expr_ref prop = fixup_clauses(ri.m_body); - func_decl_ref_vector const& sig = ri.m_vars; - expr_ref q(m); - expr_ref_vector sig_vars(m); - for (unsigned j = 0; j < sig.size(); ++j) { - sig_vars.push_back(m.mk_const(sig[sig.size()-j-1])); - } - expr_abstract(m, 0, sig_vars.size(), sig_vars.c_ptr(), prop, q); - if (sig.empty()) { - md->register_decl(pred, q); - } - else { - func_interp* fi = alloc(func_interp, m, sig.size()); - fi->set_else(q); - md->register_decl(pred, fi); - } - } - TRACE("pdr", model_smt2_pp(tout, m, *md, 0);); - apply(const_cast(m_mc), md); - } - - expr_ref inductive_property::to_expr() const { - model_ref md; - expr_ref result(m); - to_model(md); - model2expr(md, result); - return result; - } - - - void inductive_property::display(datalog::rule_manager& rm, ptr_vector const& rules, std::ostream& out) const { - func_decl_set bound_decls, aux_decls; - collect_decls_proc collect_decls(bound_decls, aux_decls); - - for (unsigned i = 0; i < m_relation_info.size(); ++i) { - bound_decls.insert(m_relation_info[i].m_pred); - func_decl_ref_vector const& sig = m_relation_info[i].m_vars; - for (unsigned j = 0; j < sig.size(); ++j) { - bound_decls.insert(sig[j]); - } - for_each_expr(collect_decls, m_relation_info[i].m_body); - } - for (unsigned i = 0; i < rules.size(); ++i) { - bound_decls.insert(rules[i]->get_decl()); - } - for (unsigned i = 0; i < rules.size(); ++i) { - unsigned u_sz = rules[i]->get_uninterpreted_tail_size(); - unsigned t_sz = rules[i]->get_tail_size(); - for (unsigned j = u_sz; j < t_sz; ++j) { - for_each_expr(collect_decls, rules[i]->get_tail(j)); - } - } - smt2_pp_environment_dbg env(m); - func_decl_set::iterator it = aux_decls.begin(), end = aux_decls.end(); - for (; it != end; ++it) { - func_decl* f = *it; - ast_smt2_pp(out, f, env); - out << "\n"; - } - - out << to_string() << "\n"; - for (unsigned i = 0; i < rules.size(); ++i) { - out << "(push)\n"; - out << "(assert (not\n"; - rm.display_smt2(*rules[i], out); - out << "))\n"; - out << "(check-sat)\n"; - out << "(pop)\n"; - } - } - - manager::manager(smt_params& fparams, unsigned max_num_contexts, ast_manager& manager) : - m(manager), - m_fparams(fparams), - m_brwr(m), - m_mux(m), - m_background(m.mk_true(), m), - m_contexts(fparams, max_num_contexts, m), - m_next_unique_num(0) - { - } - - - void manager::add_new_state(func_decl * s) { - SASSERT(s->get_arity()==0); //we currently don't support non-constant states - decl_vector vect; - SASSERT(o_index(0)==1); //we assume this in the number of retrieved symbols - m_mux.create_tuple(s, s->get_arity(), s->get_domain(), s->get_range(), 2, vect); - m_o0_preds.push_back(vect[o_index(0)]); - } - - func_decl * manager::get_o_pred(func_decl* s, unsigned idx) - { - func_decl * res = m_mux.try_get_by_prefix(s, o_index(idx)); - if(res) { return res; } - add_new_state(s); - res = m_mux.try_get_by_prefix(s, o_index(idx)); - SASSERT(res); - return res; - } - - func_decl * manager::get_n_pred(func_decl* s) - { - func_decl * res = m_mux.try_get_by_prefix(s, n_index()); - if(res) { return res; } - add_new_state(s); - res = m_mux.try_get_by_prefix(s, n_index()); - SASSERT(res); - return res; - } - - void manager::mk_model_into_cube(const expr_ref_vector & mdl, expr_ref & res) { - m_brwr.mk_and(mdl.size(), mdl.c_ptr(), res); - } - - void manager::mk_core_into_cube(const expr_ref_vector & core, expr_ref & res) { - m_brwr.mk_and(core.size(), core.c_ptr(), res); - } - - void manager::mk_cube_into_lemma(expr * cube, expr_ref & res) { - m_brwr.mk_not(cube, res); - } - - void manager::mk_lemma_into_cube(expr * lemma, expr_ref & res) { - m_brwr.mk_not(lemma, res); - } - - expr_ref manager::mk_and(unsigned sz, expr* const* exprs) { - expr_ref result(m); - m_brwr.mk_and(sz, exprs, result); - return result; - } - - expr_ref manager::mk_or(unsigned sz, expr* const* exprs) { - expr_ref result(m); - m_brwr.mk_or(sz, exprs, result); - return result; - } - - expr_ref manager::mk_not_and(expr_ref_vector const& conjs) { - expr_ref result(m), e(m); - expr_ref_vector es(conjs); - flatten_and(es); - for (unsigned i = 0; i < es.size(); ++i) { - m_brwr.mk_not(es[i].get(), e); - es[i] = e; - } - m_brwr.mk_or(es.size(), es.c_ptr(), result); - return result; - } - - void manager::get_or(expr* e, expr_ref_vector& result) { - result.push_back(e); - for (unsigned i = 0; i < result.size(); ) { - e = result[i].get(); - if (m.is_or(e)) { - result.append(to_app(e)->get_num_args(), to_app(e)->get_args()); - result[i] = result.back(); - result.pop_back(); - } - else { - ++i; - } - } - } - - bool manager::try_get_state_and_value_from_atom(expr * atom0, app *& state, app_ref& value) - { - if(!is_app(atom0)) { - return false; - } - app * atom = to_app(atom0); - expr * arg1; - expr * arg2; - app * candidate_state; - app_ref candidate_value(m); - if(m.is_not(atom, arg1)) { - if(!is_app(arg1)) { - return false; - } - candidate_state = to_app(arg1); - candidate_value = m.mk_false(); - } - else if(m.is_eq(atom, arg1, arg2)) { - if(!is_app(arg1) || !is_app(arg2)) { - return false; - } - if(!m_mux.is_muxed(to_app(arg1)->get_decl())) { - std::swap(arg1, arg2); - } - candidate_state = to_app(arg1); - candidate_value = to_app(arg2); - } - else { - candidate_state = atom; - candidate_value = m.mk_true(); - } - if(!m_mux.is_muxed(candidate_state->get_decl())) { - return false; - } - state = candidate_state; - value = candidate_value; - return true; - } - - bool manager::try_get_state_decl_from_atom(expr * atom, func_decl *& state) { - app_ref dummy_value_holder(m); - app * s; - if(try_get_state_and_value_from_atom(atom, s, dummy_value_holder)) { - state = s->get_decl(); - return true; - } - else { - return false; - } - } - - bool manager::implication_surely_holds(expr * lhs, expr * rhs, expr * bg) { - smt::kernel sctx(m, get_fparams()); - if(bg) { - sctx.assert_expr(bg); - } - sctx.assert_expr(lhs); - expr_ref neg_rhs(m.mk_not(rhs),m); - sctx.assert_expr(neg_rhs); - lbool smt_res = sctx.check(); - return smt_res==l_false; - } - -}; diff --git a/src/muz/pdr/pdr_manager.h b/src/muz/pdr/pdr_manager.h deleted file mode 100644 index ccbbbe356..000000000 --- a/src/muz/pdr/pdr_manager.h +++ /dev/null @@ -1,304 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_manager.h - -Abstract: - - A manager class for PDR, taking care of creating of AST - objects and conversions between them. - -Author: - - Krystof Hoder (t-khoder) 2011-8-25. - -Revision History: - ---*/ - -#ifndef PDR_MANAGER_H_ -#define PDR_MANAGER_H_ - -#include -#include -#include "ast/rewriter/bool_rewriter.h" -#include "ast/rewriter/expr_replacer.h" -#include "ast/expr_substitution.h" -#include "util/map.h" -#include "util/ref_vector.h" -#include "smt/smt_kernel.h" -#include "muz/pdr/pdr_util.h" -#include "muz/pdr/pdr_sym_mux.h" -#include "muz/pdr/pdr_farkas_learner.h" -#include "muz/pdr/pdr_smt_context_manager.h" -#include "muz/base/dl_rule.h" - - -namespace smt { - class context; -} - -namespace pdr { - - struct relation_info { - func_decl_ref m_pred; - func_decl_ref_vector m_vars; - expr_ref m_body; - relation_info(ast_manager& m, func_decl* pred, ptr_vector const& vars, expr* b): - m_pred(pred, m), m_vars(m, vars.size(), vars.c_ptr()), m_body(b, m) {} - relation_info(relation_info const& other): m_pred(other.m_pred), m_vars(other.m_vars), m_body(other.m_body) {} - }; - - class unknown_exception {}; - - class inductive_property { - ast_manager& m; - model_converter_ref m_mc; - vector m_relation_info; - expr_ref fixup_clauses(expr* property) const; - expr_ref fixup_clause(expr* clause) const; - public: - inductive_property(ast_manager& m, model_converter_ref& mc, vector const& relations): - m(m), - m_mc(mc), - m_relation_info(relations) {} - - std::string to_string() const; - - expr_ref to_expr() const; - - void to_model(model_ref& md) const; - - void display(datalog::rule_manager& rm, ptr_vector const& rules, std::ostream& out) const; - }; - - class manager - { - ast_manager& m; - smt_params& m_fparams; - - mutable bool_rewriter m_brwr; - - sym_mux m_mux; - expr_ref m_background; - decl_vector m_o0_preds; - pdr::smt_context_manager m_contexts; - - /** whenever we need an unique number, we get this one and increase */ - unsigned m_next_unique_num; - - - unsigned n_index() const { return 0; } - unsigned o_index(unsigned i) const { return i+1; } - - void add_new_state(func_decl * s); - - public: - manager(smt_params& fparams, unsigned max_num_contexts, ast_manager & manager); - - ast_manager& get_manager() const { return m; } - smt_params& get_fparams() const { return m_fparams; } - bool_rewriter& get_brwr() const { return m_brwr; } - - expr_ref mk_and(unsigned sz, expr* const* exprs); - expr_ref mk_and(expr_ref_vector const& exprs) { - return mk_and(exprs.size(), exprs.c_ptr()); - } - expr_ref mk_and(expr* a, expr* b) { - expr* args[2] = { a, b }; - return mk_and(2, args); - } - expr_ref mk_or(unsigned sz, expr* const* exprs); - expr_ref mk_or(expr_ref_vector const& exprs) { - return mk_or(exprs.size(), exprs.c_ptr()); - } - - expr_ref mk_not_and(expr_ref_vector const& exprs); - - void get_or(expr* e, expr_ref_vector& result); - - //"o" predicates stand for the old states and "n" for the new states - func_decl * get_o_pred(func_decl * s, unsigned idx); - func_decl * get_n_pred(func_decl * s); - - /** - Marks symbol as non-model which means it will not appear in models collected by - get_state_cube_from_model function. - This is to take care of auxiliary symbols introduced by the disjunction relations - to relativize lemmas coming from disjuncts. - */ - void mark_as_non_model(func_decl * p) { - m_mux.mark_as_non_model(p); - } - - - func_decl * const * begin_o0_preds() const { return m_o0_preds.begin(); } - func_decl * const * end_o0_preds() const { return m_o0_preds.end(); } - - bool is_state_pred(func_decl * p) const { return m_mux.is_muxed(p); } - func_decl * to_o0(func_decl * p) { return m_mux.conv(m_mux.get_primary(p), 0, o_index(0)); } - - bool is_o(func_decl * p, unsigned idx) const { - return m_mux.has_index(p, o_index(idx)); - } - bool is_o(expr* e, unsigned idx) const { - return is_app(e) && is_o(to_app(e)->get_decl(), idx); - } - bool is_o(func_decl * p) const { - unsigned idx; - return m_mux.try_get_index(p, idx) && idx!=n_index(); - } - bool is_o(expr* e) const { - return is_app(e) && is_o(to_app(e)->get_decl()); - } - bool is_n(func_decl * p) const { - return m_mux.has_index(p, n_index()); - } - bool is_n(expr* e) const { - return is_app(e) && is_n(to_app(e)->get_decl()); - } - - /** true if p should not appead in models propagates into child relations */ - bool is_non_model_sym(func_decl * p) const - { return m_mux.is_non_model_sym(p); } - - - /** true if f doesn't contain any n predicates */ - bool is_o_formula(expr * f) const { - return !m_mux.contains(f, n_index()); - } - - /** true if f contains only o state preds of index o_idx */ - bool is_o_formula(expr * f, unsigned o_idx) const { - return m_mux.is_homogenous_formula(f, o_index(o_idx)); - } - /** true if f doesn't contain any o predicates */ - bool is_n_formula(expr * f) const { - return m_mux.is_homogenous_formula(f, n_index()); - } - - func_decl * o2n(func_decl * p, unsigned o_idx) { - return m_mux.conv(p, o_index(o_idx), n_index()); - } - func_decl * o2o(func_decl * p, unsigned src_idx, unsigned tgt_idx) { - return m_mux.conv(p, o_index(src_idx), o_index(tgt_idx)); - } - func_decl * n2o(func_decl * p, unsigned o_idx) { - return m_mux.conv(p, n_index(), o_index(o_idx)); - } - - void formula_o2n(expr * f, expr_ref & result, unsigned o_idx, bool homogenous=true) - { m_mux.conv_formula(f, o_index(o_idx), n_index(), result, homogenous); } - - void formula_n2o(expr * f, expr_ref & result, unsigned o_idx, bool homogenous=true) - { m_mux.conv_formula(f, n_index(), o_index(o_idx), result, homogenous); } - - void formula_n2o(unsigned o_idx, bool homogenous, expr_ref & result) - { m_mux.conv_formula(result.get(), n_index(), o_index(o_idx), result, homogenous); } - - void formula_o2o(expr * src, expr_ref & tgt, unsigned src_idx, unsigned tgt_idx, bool homogenous=true) - { m_mux.conv_formula(src, o_index(src_idx), o_index(tgt_idx), tgt, homogenous); } - - /** - Return true if all state symbols which e contains are of one kind (either "n" or one of "o"). - */ - bool is_homogenous_formula(expr * e) const { - return m_mux.is_homogenous_formula(e); - } - - /** - Collect indices used in expression. - */ - void collect_indices(expr* e, unsigned_vector& indices) const { - m_mux.collect_indices(e, indices); - } - - /** - Collect used variables of each index. - */ - void collect_variables(expr* e, vector >& vars) const { - m_mux.collect_variables(e, vars); - } - - /** - Return true iff both s1 and s2 are either "n" or "o" of the same index. - If one (or both) of them are not state symbol, return false. - */ - bool have_different_state_kinds(func_decl * s1, func_decl * s2) const { - unsigned i1, i2; - return m_mux.try_get_index(s1, i1) && m_mux.try_get_index(s2, i2) && i1!=i2; - } - - /** - Increase indexes of state symbols in formula by dist. - The 'N' index becomes 'O' index with number dist-1. - */ - void formula_shift(expr * src, expr_ref & tgt, unsigned dist) { - SASSERT(n_index()==0); - SASSERT(o_index(0)==1); - m_mux.shift_formula(src, dist, tgt); - } - - void mk_model_into_cube(const expr_ref_vector & mdl, expr_ref & res); - void mk_core_into_cube(const expr_ref_vector & core, expr_ref & res); - void mk_cube_into_lemma(expr * cube, expr_ref & res); - void mk_lemma_into_cube(expr * lemma, expr_ref & res); - - /** - Remove from vec all atoms that do not have an "o" state. - The order of elements in vec may change. - An assumption is that atoms having "o" state of given index - do not have "o" states of other indexes or "n" states. - */ - void filter_o_atoms(expr_ref_vector& vec, unsigned o_idx) const - { m_mux.filter_idx(vec, o_index(o_idx)); } - void filter_n_atoms(expr_ref_vector& vec) const - { m_mux.filter_idx(vec, n_index()); } - - /** - Partition literals into o_lits and others. - */ - void partition_o_atoms(expr_ref_vector const& lits, - expr_ref_vector& o_lits, - expr_ref_vector& other, - unsigned o_idx) const { - m_mux.partition_o_idx(lits, o_lits, other, o_index(o_idx)); - } - - void filter_out_non_model_atoms(expr_ref_vector& vec) const - { m_mux.filter_non_model_lits(vec); } - - bool try_get_state_and_value_from_atom(expr * atom, app *& state, app_ref& value); - bool try_get_state_decl_from_atom(expr * atom, func_decl *& state); - - - std::string pp_model(const model_core & mdl) const - { return m_mux.pp_model(mdl); } - - - void set_background(expr* b) { m_background = b; } - - expr* get_background() const { return m_background; } - - - /** - Return true if we can show that lhs => rhs. The function can have false negatives - (i.e. when smt::context returns unknown), but no false positives. - - bg is background knowledge and can be null - */ - bool implication_surely_holds(expr * lhs, expr * rhs, expr * bg=nullptr); - - unsigned get_unique_num() { return m_next_unique_num++; } - - pdr::smt_context* mk_fresh() { return m_contexts.mk_fresh(); } - - void collect_statistics(statistics& st) const { m_contexts.collect_statistics(st); } - - void reset_statistics() { m_contexts.reset_statistics(); } - }; -} - -#endif diff --git a/src/muz/pdr/pdr_prop_solver.cpp b/src/muz/pdr/pdr_prop_solver.cpp deleted file mode 100644 index 8ebc9b9cb..000000000 --- a/src/muz/pdr/pdr_prop_solver.cpp +++ /dev/null @@ -1,459 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - prop_solver.cpp - -Abstract: - - SMT solver abstraction for PDR. - -Author: - - Krystof Hoder (t-khoder) 2011-8-17. - -Revision History: - ---*/ - -#include -#include "model/model.h" -#include "muz/pdr/pdr_util.h" -#include "muz/pdr/pdr_prop_solver.h" -#include "ast/ast_smt2_pp.h" -#include "muz/base/dl_util.h" -#include "model/model_pp.h" -#include "smt/params/smt_params.h" -#include "ast/datatype_decl_plugin.h" -#include "ast/bv_decl_plugin.h" -#include "muz/pdr/pdr_farkas_learner.h" -#include "ast/ast_smt2_pp.h" -#include "ast/rewriter/expr_replacer.h" - -// -// Auxiliary structure to introduce propositional names for assumptions that are not -// propositional. It is to work with the smt::context's restriction -// that assumptions be propositional literals. -// - -namespace pdr { - - class prop_solver::safe_assumptions { - prop_solver& s; - ast_manager& m; - expr_ref_vector m_atoms; - expr_ref_vector m_assumptions; - obj_map m_proxies2expr; - obj_map m_expr2proxies; - unsigned m_num_proxies; - - app * mk_proxy(expr* literal) { - app* res; - SASSERT(!is_var(literal)); //it doesn't make sense to introduce names to variables - if (m_expr2proxies.find(literal, res)) { - return res; - } - SASSERT(s.m_proxies.size() >= m_num_proxies); - if (m_num_proxies == s.m_proxies.size()) { - std::stringstream name; - name << "pdr_proxy_" << s.m_proxies.size(); - res = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); - s.m_proxies.push_back(res); - s.m_aux_symbols.insert(res->get_decl()); - } - else { - res = s.m_proxies[m_num_proxies].get(); - } - ++m_num_proxies; - m_expr2proxies.insert(literal, res); - m_proxies2expr.insert(res, literal); - expr_ref implies(m.mk_or(m.mk_not(res), literal), m); - s.m_ctx->assert_expr(implies); - m_assumptions.push_back(implies); - TRACE("pdr_verbose", tout << "name asserted " << mk_pp(implies, m) << "\n";); - return res; - } - - void mk_safe(expr_ref_vector& conjs) { - flatten_and(conjs); - expand_literals(conjs); - for (unsigned i = 0; i < conjs.size(); ++i) { - expr * lit = conjs[i].get(); - expr * lit_core = lit; - m.is_not(lit, lit_core); - SASSERT(!m.is_true(lit)); - if (!is_uninterp(lit_core) || to_app(lit_core)->get_num_args() != 0) { - conjs[i] = mk_proxy(lit); - } - } - m_assumptions.append(conjs); - } - - expr* apply_accessor( - ptr_vector const& acc, - unsigned j, - func_decl* f, - expr* c) { - if (is_app(c) && to_app(c)->get_decl() == f) { - return to_app(c)->get_arg(j); - } - else { - return m.mk_app(acc[j], c); - } - } - - void expand_literals(expr_ref_vector& conjs) { - arith_util arith(m); - datatype_util dt(m); - bv_util bv(m); - expr* e1, *e2, *c, *val; - rational r; - unsigned bv_size; - - TRACE("pdr", - tout << "begin expand\n"; - for (unsigned i = 0; i < conjs.size(); ++i) { - tout << mk_pp(conjs[i].get(), m) << "\n"; - }); - - for (unsigned i = 0; i < conjs.size(); ++i) { - expr* e = conjs[i].get(); - if (m.is_eq(e, e1, e2) && arith.is_int_real(e1)) { - conjs[i] = arith.mk_le(e1,e2); - if (i+1 == conjs.size()) { - conjs.push_back(arith.mk_ge(e1, e2)); - } - else { - conjs.push_back(conjs[i+1].get()); - conjs[i+1] = arith.mk_ge(e1, e2); - } - ++i; - } - else if ((m.is_eq(e, c, val) && is_app(val) && dt.is_constructor(to_app(val))) || - (m.is_eq(e, val, c) && is_app(val) && dt.is_constructor(to_app(val)))){ - func_decl* f = to_app(val)->get_decl(); - func_decl* r = dt.get_constructor_is(f); - conjs[i] = m.mk_app(r, c); - ptr_vector const& acc = *dt.get_constructor_accessors(f); - for (unsigned j = 0; j < acc.size(); ++j) { - conjs.push_back(m.mk_eq(apply_accessor(acc, j, f, c), to_app(val)->get_arg(j))); - } - } - else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || - (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { - rational two(2); - for (unsigned j = 0; j < bv_size; ++j) { - parameter p(j); - //expr* e = m.mk_app(bv.get_family_id(), OP_BIT2BOOL, 1, &p, 1, &c); - expr* e = m.mk_eq(m.mk_app(bv.get_family_id(), OP_BIT1), bv.mk_extract(j, j, c)); - if ((r % two).is_zero()) { - e = m.mk_not(e); - } - r = div(r, two); - if (j == 0) { - conjs[i] = e; - } - else { - conjs.push_back(e); - } - } - } - } - TRACE("pdr", - tout << "end expand\n"; - for (unsigned i = 0; i < conjs.size(); ++i) { - tout << mk_pp(conjs[i].get(), m) << "\n"; - }); - } - - public: - safe_assumptions(prop_solver& s, expr_ref_vector const& assumptions): - s(s), m(s.m), m_atoms(assumptions), m_assumptions(m), m_num_proxies(0) { - mk_safe(m_atoms); - } - - ~safe_assumptions() { - } - - expr_ref_vector const& atoms() const { return m_atoms; } - - unsigned assumptions_size() const { return m_assumptions.size(); } - - expr* assumptions(unsigned i) const { return m_assumptions[i]; } - - void undo_proxies(expr_ref_vector& es) { - expr_ref e(m); - expr* r; - for (unsigned i = 0; i < es.size(); ++i) { - e = es[i].get(); - if (is_app(e) && m_proxies2expr.find(to_app(e), r)) { - es[i] = r; - } - } - } - - void elim_proxies(expr_ref_vector& es) { - expr_substitution sub(m, false, m.proofs_enabled()); - proof_ref pr(m); - if (m.proofs_enabled()) { - pr = m.mk_asserted(m.mk_true()); - } - obj_map::iterator it = m_proxies2expr.begin(), end = m_proxies2expr.end(); - for (; it != end; ++it) { - sub.insert(it->m_key, m.mk_true(), pr); - } - scoped_ptr rep = mk_default_expr_replacer(m); - rep->set_substitution(&sub); - replace_proxies(*rep, es); - } - private: - - void replace_proxies(expr_replacer& rep, expr_ref_vector& es) { - expr_ref e(m); - for (unsigned i = 0; i < es.size(); ++i) { - e = es[i].get(); - rep(e); - es[i] = e; - if (m.is_true(e)) { - es[i] = es.back(); - es.pop_back(); - --i; - } - } - } - }; - - - prop_solver::prop_solver(manager& pm, symbol const& name) : - m_fparams(pm.get_fparams()), - m(pm.get_manager()), - m_pm(pm), - m_name(name), - m_ctx(pm.mk_fresh()), - m_pos_level_atoms(m), - m_neg_level_atoms(m), - m_proxies(m), - m_core(nullptr), - m_model(nullptr), - m_consequences(nullptr), - m_subset_based_core(false), - m_use_farkas(false), - m_in_level(false), - m_current_level(0) - { - m_ctx->assert_expr(m_pm.get_background()); - } - - void prop_solver::add_level() { - unsigned idx = level_cnt(); - std::stringstream name; - name << m_name << "#level_" << idx; - func_decl * lev_pred = m.mk_fresh_func_decl(name.str().c_str(), 0, nullptr,m.mk_bool_sort()); - m_aux_symbols.insert(lev_pred); - m_level_preds.push_back(lev_pred); - - app_ref pos_la(m.mk_const(lev_pred), m); - app_ref neg_la(m.mk_not(pos_la.get()), m); - - m_pos_level_atoms.push_back(pos_la); - m_neg_level_atoms.push_back(neg_la); - - m_level_atoms_set.insert(pos_la.get()); - m_level_atoms_set.insert(neg_la.get()); - } - - void prop_solver::ensure_level(unsigned lvl) { - while (lvl>=level_cnt()) { - add_level(); - } - } - - unsigned prop_solver::level_cnt() const { - return m_level_preds.size(); - } - - void prop_solver::push_level_atoms(unsigned level, expr_ref_vector& tgt) const { - unsigned lev_cnt = level_cnt(); - for (unsigned i=0; i=level; - app * lev_atom = active ? m_neg_level_atoms[i] : m_pos_level_atoms[i]; - tgt.push_back(lev_atom); - } - } - - void prop_solver::add_formula(expr * form) { - SASSERT(!m_in_level); - m_ctx->assert_expr(form); - IF_VERBOSE(21, verbose_stream() << "$ asserted " << mk_pp(form, m) << "\n";); - TRACE("pdr", tout << "add_formula: " << mk_pp(form, m) << "\n";); - } - - void prop_solver::add_level_formula(expr * form, unsigned level) { - ensure_level(level); - app * lev_atom = m_pos_level_atoms[level].get(); - app_ref lform(m.mk_or(form, lev_atom), m); - add_formula(lform.get()); - } - - - lbool prop_solver::check_safe_assumptions( - safe_assumptions& safe, - const expr_ref_vector& atoms) - { - flet _model(m_fparams.m_model, m_model != nullptr); - expr_ref_vector expr_atoms(m); - expr_atoms.append(atoms.size(), atoms.c_ptr()); - - if (m_in_level) { - push_level_atoms(m_current_level, expr_atoms); - } - - lbool result = m_ctx->check(expr_atoms); - - TRACE("pdr", - tout << mk_pp(m_pm.mk_and(expr_atoms), m) << "\n"; - tout << result << "\n";); - - if (result == l_true && m_model) { - m_ctx->get_model(*m_model); - TRACE("pdr_verbose", model_pp(tout, **m_model); ); - } - - if (result == l_false) { - unsigned core_size = m_ctx->get_unsat_core_size(); - m_assumes_level = false; - for (unsigned i = 0; i < core_size; ++i) { - if (m_level_atoms_set.contains(m_ctx->get_unsat_core_expr(i))) { - m_assumes_level = true; - break; - } - } - } - - if (result == l_false && - m_core && - m.proofs_enabled() && - m_use_farkas && - !m_subset_based_core) { - extract_theory_core(safe); - } - else if (result == l_false && m_core) { - extract_subset_core(safe); - SASSERT(expr_atoms.size() >= m_core->size()); - } - m_core = nullptr; - m_model = nullptr; - m_subset_based_core = false; - return result; - } - - void prop_solver::extract_subset_core(safe_assumptions& safe) { - unsigned core_size = m_ctx->get_unsat_core_size(); - m_core->reset(); - for (unsigned i = 0; i < core_size; ++i) { - expr * core_expr = m_ctx->get_unsat_core_expr(i); - SASSERT(is_app(core_expr)); - - if (m_level_atoms_set.contains(core_expr)) { - continue; - } - if (m_ctx->is_aux_predicate(core_expr)) { - continue; - } - m_core->push_back(to_app(core_expr)); - } - - safe.undo_proxies(*m_core); - - TRACE("pdr", - tout << "core_exprs: "; - for (unsigned i = 0; i < core_size; ++i) { - tout << mk_pp(m_ctx->get_unsat_core_expr(i), m) << " "; - } - tout << "\n"; - tout << "core: " << mk_pp(m_pm.mk_and(*m_core), m) << "\n"; - ); - } - - - void prop_solver::extract_theory_core(safe_assumptions& safe) { - proof_ref pr(m); - pr = m_ctx->get_proof(); - IF_VERBOSE(21, verbose_stream() << mk_ismt2_pp(pr, m) << "\n";); - farkas_learner fl(m_fparams, m); - expr_ref_vector lemmas(m); - obj_hashtable bs; - for (unsigned i = 0; i < safe.assumptions_size(); ++i) { - bs.insert(safe.assumptions(i)); - } - fl.get_lemmas(pr, bs, lemmas); - safe.elim_proxies(lemmas); - fl.simplify_lemmas(lemmas); // redundant? - - bool outside_of_logic = - (m_fparams.m_arith_mode == AS_DIFF_LOGIC && - !is_difference_logic(m, lemmas.size(), lemmas.c_ptr())) || - (m_fparams.m_arith_mode == AS_UTVPI && - !is_utvpi_logic(m, lemmas.size(), lemmas.c_ptr())); - - if (outside_of_logic) { - IF_VERBOSE(2, - verbose_stream() << "not diff\n"; - for (unsigned i = 0; i < lemmas.size(); ++i) { - verbose_stream() << mk_pp(lemmas[i].get(), m) << "\n"; - }); - extract_subset_core(safe); - } - else { - - IF_VERBOSE(2, - verbose_stream() << "Lemmas\n"; - for (unsigned i = 0; i < lemmas.size(); ++i) { - verbose_stream() << mk_pp(lemmas[i].get(), m) << "\n"; - }); - - m_core->reset(); - m_core->append(lemmas); - - if (m_consequences) { - fl.get_consequences(pr, bs, *m_consequences); - } - } - } - - lbool prop_solver::check_assumptions(const expr_ref_vector & atoms) { - return check_assumptions_and_formula(atoms, m.mk_true()); - } - - lbool prop_solver::check_conjunction_as_assumptions(expr * conj) { - expr_ref_vector asmp(m); - asmp.push_back(conj); - return check_assumptions(asmp); - } - - lbool prop_solver::check_assumptions_and_formula(const expr_ref_vector & atoms, expr * form) - { - pdr::smt_context::scoped _scoped(*m_ctx); - safe_assumptions safe(*this, atoms); - m_ctx->assert_expr(form); - CTRACE("pdr", !m.is_true(form), tout << "check with formula: " << mk_pp(form, m) << "\n";); - lbool res = check_safe_assumptions(safe, safe.atoms()); - - // - // we don't have to undo model naming, as from the model - // we extract the values for state variables directly - // - return res; - } - - void prop_solver::collect_statistics(statistics& st) const { - } - - void prop_solver::reset_statistics() { - } - - - - -} diff --git a/src/muz/pdr/pdr_prop_solver.h b/src/muz/pdr/pdr_prop_solver.h deleted file mode 100644 index 463163fbd..000000000 --- a/src/muz/pdr/pdr_prop_solver.h +++ /dev/null @@ -1,139 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - prop_solver.h - -Abstract: - - SAT solver abstraction for PDR. - -Author: - - Krystof Hoder (t-khoder) 2011-8-17. - -Revision History: - ---*/ - -#ifndef PROP_SOLVER_H_ -#define PROP_SOLVER_H_ - -#include -#include -#include -#include "ast/ast.h" -#include "util/obj_hashtable.h" -#include "smt/smt_kernel.h" -#include "util/util.h" -#include "util/vector.h" -#include "muz/pdr/pdr_manager.h" -#include "muz/pdr/pdr_smt_context_manager.h" - - -namespace pdr { - class prop_solver { - - private: - smt_params& m_fparams; - ast_manager& m; - manager& m_pm; - symbol m_name; - scoped_ptr m_ctx; - decl_vector m_level_preds; - app_ref_vector m_pos_level_atoms; // atoms used to identify level - app_ref_vector m_neg_level_atoms; // - obj_hashtable m_level_atoms_set; - app_ref_vector m_proxies; // predicates for assumptions - expr_ref_vector* m_core; - model_ref* m_model; - expr_ref_vector* m_consequences; - bool m_subset_based_core; - bool m_assumes_level; - bool m_use_farkas; - func_decl_set m_aux_symbols; - bool m_in_level; - unsigned m_current_level; // set when m_in_level - - /** Add level atoms activating certain level into a vector */ - void push_level_atoms(unsigned level, expr_ref_vector & tgt) const; - - void ensure_level(unsigned lvl); - - class safe_assumptions; - - void extract_theory_core(safe_assumptions& assumptions); - - void extract_subset_core(safe_assumptions& assumptions); - - lbool check_safe_assumptions( - safe_assumptions& assumptions, - expr_ref_vector const& atoms); - - - public: - prop_solver(pdr::manager& pm, symbol const& name); - - /** return true is s is a symbol introduced by prop_solver */ - bool is_aux_symbol(func_decl * s) const { - return - m_aux_symbols.contains(s) || - m_ctx->is_aux_predicate(s); - } - - void set_core(expr_ref_vector* core) { m_core = core; } - void set_model(model_ref* mdl) { m_model = mdl; } - void set_subset_based_core(bool f) { m_subset_based_core = f; } - void set_consequences(expr_ref_vector* consequences) { m_consequences = consequences; } - - bool assumes_level() const { return m_assumes_level; } - - void add_level(); - unsigned level_cnt() const; - - class scoped_level { - bool& m_lev; - public: - scoped_level(prop_solver& ps, unsigned lvl):m_lev(ps.m_in_level) { - SASSERT(!m_lev); m_lev = true; ps.m_current_level = lvl; - } - ~scoped_level() { m_lev = false; } - }; - - void set_use_farkas(bool f) { m_use_farkas = f; } - bool get_use_farkas() const { return m_use_farkas; } - - void add_formula(expr * form); - void add_level_formula(expr * form, unsigned level); - - /** - * Return true iff conjunction of atoms is consistent with the current state of - * the solver. - * - * If the conjunction of atoms is inconsistent with the solver state and core is non-zero, - * core will contain an unsatisfiable core of atoms. - * - * If the conjunction of atoms is consistent with the solver state and o_model is non-zero, - * o_model will contain the "o" literals true in the assignment. - */ - lbool check_assumptions(const expr_ref_vector & atoms); - - lbool check_conjunction_as_assumptions(expr * conj); - - /** - * Like check_assumptions, except it also asserts an extra formula - */ - lbool check_assumptions_and_formula( - const expr_ref_vector & atoms, - expr * form); - - void collect_statistics(statistics& st) const; - - void reset_statistics(); - - }; -} - - -#endif diff --git a/src/muz/pdr/pdr_reachable_cache.cpp b/src/muz/pdr/pdr_reachable_cache.cpp deleted file mode 100644 index 5d553df4d..000000000 --- a/src/muz/pdr/pdr_reachable_cache.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - reachable_cache.cpp - -Abstract: - - Object for caching of reachable states. - -Author: - - Krystof Hoder (t-khoder) 2011-9-14. - -Revision History: - ---*/ - -#include "muz/pdr/pdr_reachable_cache.h" - -namespace pdr { - - reachable_cache::reachable_cache(pdr::manager & pm, datalog::PDR_CACHE_MODE cm) - : m(pm.get_manager()), - m_pm(pm), - m_ctx(nullptr), - m_ref_holder(m), - m_disj_connector(m), - m_cache_mode(cm) { - if (m_cache_mode == datalog::CONSTRAINT_CACHE) { - m_ctx = pm.mk_fresh(); - m_ctx->assert_expr(m_pm.get_background()); - } - } - - - void reachable_cache::add_disjuncted_formula(expr * f) { - app_ref new_connector(m.mk_fresh_const("disj_conn", m.mk_bool_sort()), m); - app_ref neg_new_connector(m.mk_not(new_connector), m); - app_ref extended_form(m); - - if(m_disj_connector) { - extended_form = m.mk_or(m_disj_connector, neg_new_connector, f); - } - else { - extended_form = m.mk_or(neg_new_connector, f); - } - if (m_ctx) { - m_ctx->assert_expr(extended_form); - } - - m_disj_connector = new_connector; - } - - void reachable_cache::add_reachable(expr * cube) { - - switch (m_cache_mode) { - case datalog::NO_CACHE: - break; - - case datalog::HASH_CACHE: - m_stats.m_inserts++; - m_cache.insert(cube); - m_ref_holder.push_back(cube); - break; - - case datalog::CONSTRAINT_CACHE: - m_stats.m_inserts++; - TRACE("pdr", tout << mk_pp(cube, m) << "\n";); - add_disjuncted_formula(cube); - break; - - default: - UNREACHABLE(); - } - } - - bool reachable_cache::is_reachable(expr * cube) { - bool found = false; - switch (m_cache_mode) { - case datalog::NO_CACHE: - return false; - - case datalog::HASH_CACHE: - found = m_cache.contains(cube); - break; - - case datalog::CONSTRAINT_CACHE: { - if(!m_disj_connector) { - found = false; - break; - } - expr * connector = m_disj_connector.get(); - expr_ref_vector assms(m); - assms.push_back(connector); - m_ctx->push(); - m_ctx->assert_expr(cube); - lbool res = m_ctx->check(assms); - m_ctx->pop(); - - TRACE("pdr", tout << "is_reachable: " << res << " " << mk_pp(cube, m) << "\n";); - - found = res == l_true; - break; - } - - default: - UNREACHABLE(); - break; - } - if (found) { - m_stats.m_hits++; - } - else { - m_stats.m_miss++; - } - return found; - } - - void reachable_cache::collect_statistics(statistics& st) const { - st.update("cache inserts", m_stats.m_inserts); - st.update("cache miss", m_stats.m_miss); - st.update("cache hits", m_stats.m_hits); - } - - void reachable_cache::reset_statistics() { - m_stats.reset(); - } - - -} diff --git a/src/muz/pdr/pdr_reachable_cache.h b/src/muz/pdr/pdr_reachable_cache.h deleted file mode 100644 index 0833541ba..000000000 --- a/src/muz/pdr/pdr_reachable_cache.h +++ /dev/null @@ -1,66 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - reachable_cache.h - -Abstract: - - Object for caching of reachable states. - -Author: - - Krystof Hoder (t-khoder) 2011-9-14. - -Revision History: - ---*/ - - -#ifndef REACHABLE_CACHE_H_ -#define REACHABLE_CACHE_H_ -#include "ast/ast.h" -#include "util/ref_vector.h" -#include "muz/pdr/pdr_manager.h" -#include "muz/pdr/pdr_smt_context_manager.h" - -namespace pdr { - class reachable_cache { - struct stats { - unsigned m_hits; - unsigned m_miss; - unsigned m_inserts; - stats() { reset(); } - void reset() { memset(this, 0, sizeof(*this)); } - }; - - ast_manager & m; - manager & m_pm; - scoped_ptr m_ctx; - ast_ref_vector m_ref_holder; - app_ref m_disj_connector; - obj_hashtable m_cache; - stats m_stats; - datalog::PDR_CACHE_MODE m_cache_mode; - - void add_disjuncted_formula(expr * f); - - public: - reachable_cache(pdr::manager & pm, datalog::PDR_CACHE_MODE cm); - - void add_init(app * f) { add_disjuncted_formula(f); } - - /** add cube whose all models are reachable */ - void add_reachable(expr * cube); - - /** return true if there is a model of cube which is reachable */ - bool is_reachable(expr * cube); - - void collect_statistics(statistics& st) const; - - void reset_statistics(); - }; -} - -#endif diff --git a/src/muz/pdr/pdr_smt_context_manager.cpp b/src/muz/pdr/pdr_smt_context_manager.cpp deleted file mode 100644 index 88734049b..000000000 --- a/src/muz/pdr/pdr_smt_context_manager.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_smt_context_manager.cpp - -Abstract: - - Manager of smt contexts - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-26. - -Revision History: - ---*/ - -#include "muz/pdr/pdr_smt_context_manager.h" -#include "ast/has_free_vars.h" -#include "ast/ast_pp.h" -#include "ast/ast_smt_pp.h" -#include -#include "smt/params/smt_params.h" - -namespace pdr { - - smt_context::smt_context(smt_context_manager& p, ast_manager& m, app* pred): - m_pred(pred, m), - m_parent(p), - m_in_delay_scope(false), - m_pushed(false) - {} - - bool smt_context::is_aux_predicate(func_decl* p) { - return m_parent.is_aux_predicate(p); - } - - smt_context::scoped::scoped(smt_context& ctx): m_ctx(ctx) { - SASSERT(!m_ctx.m_in_delay_scope); - SASSERT(!m_ctx.m_pushed); - m_ctx.m_in_delay_scope = true; - } - - smt_context::scoped::~scoped() { - SASSERT(m_ctx.m_in_delay_scope); - if (m_ctx.m_pushed) { - m_ctx.pop(); - m_ctx.m_pushed = false; - } - m_ctx.m_in_delay_scope = false; - } - - - _smt_context::_smt_context(smt::kernel & ctx, smt_context_manager& p, app* pred): - smt_context(p, ctx.m(), pred), - m_context(ctx) - {} - - void _smt_context::assert_expr(expr* e) { - ast_manager& m = m_context.m(); - if (m.is_true(e)) { - return; - } - CTRACE("pdr", has_free_vars(e), tout << mk_pp(e, m) << "\n";); - SASSERT(!has_free_vars(e)); - if (m_in_delay_scope && !m_pushed) { - m_context.push(); - m_pushed = true; - } - expr_ref fml(m); - fml = m_pushed?e:m.mk_implies(m_pred, e); - m_context.assert_expr(fml); - } - - lbool _smt_context::check(expr_ref_vector& assumptions) { - ast_manager& m = m_pred.get_manager(); - if (!m.is_true(m_pred)) { - assumptions.push_back(m_pred); - } - TRACE("pdr_check", - { - ast_smt_pp pp(m); - for (unsigned i = 0; i < m_context.size(); ++i) { - pp.add_assumption(m_context.get_formula(i)); - } - for (unsigned i = 0; i < assumptions.size(); ++i) { - pp.add_assumption(assumptions[i].get()); - } - - static unsigned lemma_id = 0; - std::ostringstream strm; - strm << "pdr-lemma-" << lemma_id << ".smt2"; - std::ofstream out(strm.str().c_str()); - pp.display_smt2(out, m.mk_true()); - out.close(); - lemma_id++; - tout << "pdr_check: " << strm.str() << "\n"; - }); - lbool result = m_context.check(assumptions.size(), assumptions.c_ptr()); - if (!m.is_true(m_pred)) { - assumptions.pop_back(); - } - return result; - } - - void _smt_context::get_model(model_ref& model) { - m_context.get_model(model); - } - - proof* _smt_context::get_proof() { - return m_context.get_proof(); - } - - smt_context_manager::smt_context_manager(smt_params& fp, unsigned max_num_contexts, ast_manager& m): - m_fparams(fp), - m(m), - m_max_num_contexts(max_num_contexts), - m_num_contexts(0), - m_predicate_list(m) { - } - - - smt_context_manager::~smt_context_manager() { - TRACE("pdr",tout << "\n";); - std::for_each(m_contexts.begin(), m_contexts.end(), delete_proc()); - } - - smt_context* smt_context_manager::mk_fresh() { - ++m_num_contexts; - app_ref pred(m); - smt::kernel * ctx = nullptr; - if (m_max_num_contexts == 0) { - m_contexts.push_back(alloc(smt::kernel, m, m_fparams)); - pred = m.mk_true(); - ctx = m_contexts[m_num_contexts-1]; - } - else { - if (m_contexts.size() < m_max_num_contexts) { - m_contexts.push_back(alloc(smt::kernel, m, m_fparams)); - } - std::stringstream name; - name << "#context" << m_num_contexts; - pred = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); - m_predicate_list.push_back(pred); - m_predicate_set.insert(pred->get_decl()); - ctx = m_contexts[(m_num_contexts-1)%m_max_num_contexts]; - } - return alloc(_smt_context, *ctx, *this, pred); - } - - void smt_context_manager::collect_statistics(statistics& st) const { - for (unsigned i = 0; i < m_contexts.size(); ++i) { - m_contexts[i]->collect_statistics(st); - } - } - - void smt_context_manager::reset_statistics() { - for (unsigned i = 0; i < m_contexts.size(); ++i) { - m_contexts[i]->reset_statistics(); - } - } - - -}; - diff --git a/src/muz/pdr/pdr_smt_context_manager.h b/src/muz/pdr/pdr_smt_context_manager.h deleted file mode 100644 index 747cd6457..000000000 --- a/src/muz/pdr/pdr_smt_context_manager.h +++ /dev/null @@ -1,92 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_smt_context_manager.h - -Abstract: - - Manager of smt contexts - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-26. - -Revision History: - ---*/ - -#ifndef PDR_SMT_CONTEXT_MANAGER_H_ -#define PDR_SMT_CONTEXT_MANAGER_H_ - -#include "smt/smt_kernel.h" -#include "ast/func_decl_dependencies.h" -#include "muz/base/dl_util.h" - -namespace pdr { - - class smt_context_manager; - - class smt_context { - protected: - app_ref m_pred; - smt_context_manager& m_parent; - bool m_in_delay_scope; - bool m_pushed; - public: - smt_context(smt_context_manager& p, ast_manager& m, app* pred); - virtual ~smt_context() {} - virtual void assert_expr(expr* e) = 0; - virtual lbool check(expr_ref_vector& assumptions) = 0; - virtual void get_model(model_ref& model) = 0; - virtual proof* get_proof() = 0; - virtual unsigned get_unsat_core_size() = 0; - virtual expr* get_unsat_core_expr(unsigned i) = 0; - virtual void push() = 0; - virtual void pop() = 0; - bool is_aux_predicate(func_decl* p); - bool is_aux_predicate(expr* p) { return is_app(p) && is_aux_predicate(to_app(p)->get_decl()); } - class scoped { - smt_context& m_ctx; - public: - scoped(smt_context& ctx); - ~scoped(); - }; - }; - - class _smt_context : public smt_context { - smt::kernel & m_context; - public: - _smt_context(smt::kernel & ctx, smt_context_manager& p, app* pred); - ~_smt_context() override {} - void assert_expr(expr* e) override; - lbool check(expr_ref_vector& assumptions) override; - void get_model(model_ref& model) override; - proof* get_proof() override; - void push() override { m_context.push(); } - void pop() override { m_context.pop(1); } - unsigned get_unsat_core_size() override { return m_context.get_unsat_core_size(); } - expr* get_unsat_core_expr(unsigned i) override { return m_context.get_unsat_core_expr(i); } - }; - - class smt_context_manager { - smt_params& m_fparams; - ast_manager& m; - unsigned m_max_num_contexts; - ptr_vector m_contexts; - unsigned m_num_contexts; - app_ref_vector m_predicate_list; - func_decl_set m_predicate_set; - public: - smt_context_manager(smt_params& fp, unsigned max_num_contexts, ast_manager& m); - ~smt_context_manager(); - smt_context* mk_fresh(); - void collect_statistics(statistics& st) const; - void reset_statistics(); - bool is_aux_predicate(func_decl* p) const { return m_predicate_set.contains(p); } - }; - -}; - -#endif diff --git a/src/muz/pdr/pdr_sym_mux.cpp b/src/muz/pdr/pdr_sym_mux.cpp deleted file mode 100644 index 72549f2d6..000000000 --- a/src/muz/pdr/pdr_sym_mux.cpp +++ /dev/null @@ -1,601 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - sym_mux.cpp - -Abstract: - - A symbol multiplexer that helps with having multiple versions of each of a set of symbols. - -Author: - - Krystof Hoder (t-khoder) 2011-9-8. - -Revision History: - ---*/ - -#include -#include "ast/ast_pp.h" -#include "ast/for_each_expr.h" -#include "model/model.h" -#include "ast/rewriter/rewriter.h" -#include "ast/rewriter/rewriter_def.h" -#include "muz/pdr/pdr_util.h" -#include "muz/pdr/pdr_sym_mux.h" - -using namespace pdr; - -sym_mux::sym_mux(ast_manager & m) - : m(m), m_ref_holder(m), - m_next_sym_suffix_idx(0) { - m_suffixes.push_back("_n"); - size_t suf_sz = m_suffixes.size(); - for(unsigned i = 0; i < suf_sz; ++i) { - symbol suff_sym = symbol(m_suffixes[i].c_str()); - m_used_suffixes.insert(suff_sym); - } -} - -std::string sym_mux::get_suffix(unsigned i) { - while(m_suffixes.size() <= i) { - std::string new_suffix; - symbol new_syffix_sym; - do { - std::stringstream stm; - stm<<'_'<0); - while(tuple.size()get_name().str(); - for(unsigned i=0; iget_arity()==arity); - SASSERT(tuple[i]->get_range()==range); - //domain should match as well, but we won't bother checking an array equality - } - else { - std::string name = pre+get_suffix(i); - tuple[i] = m.mk_func_decl(symbol(name.c_str()), arity, domain, range); - } - m_ref_holder.push_back(tuple[i]); - m_sym2idx.insert(tuple[i], i); - m_sym2prim.insert(tuple[i], tuple[0]); - } - - m_prim2all.insert(tuple[0], tuple); - m_prefix2prim.insert(prefix, tuple[0]); - m_prim2prefix.insert(tuple[0], prefix); - m_prim_preds.push_back(tuple[0]); - m_ref_holder.push_back(prefix); -} - -void sym_mux::ensure_tuple_size(func_decl * prim, unsigned sz) { - SASSERT(m_prim2all.contains(prim)); - decl_vector& tuple = m_prim2all.find_core(prim)->get_data().m_value; - SASSERT(tuple[0]==prim); - - if(sz <= tuple.size()) { return; } - - func_decl * prefix; - TRUSTME(m_prim2prefix.find(prim, prefix)); - std::string prefix_name = prefix->get_name().bare_str(); - for(unsigned i = tuple.size(); i < sz; ++i) { - std::string name = prefix_name + get_suffix(i); - func_decl * new_sym = m.mk_func_decl(symbol(name.c_str()), prefix->get_arity(), - prefix->get_domain(), prefix->get_range()); - - tuple.push_back(new_sym); - m_ref_holder.push_back(new_sym); - m_sym2idx.insert(new_sym, i); - m_sym2prim.insert(new_sym, prim); - } -} - -func_decl * sym_mux::conv(func_decl * sym, unsigned src_idx, unsigned tgt_idx) -{ - if(src_idx==tgt_idx) { return sym; } - func_decl * prim = (src_idx==0) ? sym : get_primary(sym); - if(tgt_idx>src_idx) { - ensure_tuple_size(prim, tgt_idx+1); - } - decl_vector & sym_vect = m_prim2all.find_core(prim)->get_data().m_value; - SASSERT(sym_vect[src_idx]==sym); - return sym_vect[tgt_idx]; -} - - -func_decl * sym_mux::get_or_create_symbol_by_prefix(func_decl* prefix, unsigned idx, - unsigned arity, sort * const * domain, sort * range) -{ - func_decl * prim = try_get_primary_by_prefix(prefix); - if(prim) { - SASSERT(prim->get_arity()==arity); - SASSERT(prim->get_range()==range); - //domain should match as well, but we won't bother checking an array equality - - return conv(prim, 0, idx); - } - - decl_vector syms; - create_tuple(prefix, arity, domain, range, idx+1, syms); - return syms[idx]; -} - -bool sym_mux::is_muxed_lit(expr * e, unsigned idx) const -{ - if(!is_app(e)) { return false; } - app * a = to_app(e); - if(m.is_not(a) && is_app(a->get_arg(0))) { - a = to_app(a->get_arg(0)); - } - return is_muxed(a->get_decl()); -} - - -struct sym_mux::formula_checker -{ - formula_checker(const sym_mux & parent, bool all, unsigned idx) : - m_parent(parent), m_all(all), m_idx(idx), - m_found_what_needed(false) - { - } - - void operator()(expr * e) - { - if(m_found_what_needed || !is_app(e)) { return; } - - func_decl * sym = to_app(e)->get_decl(); - unsigned sym_idx; - if(!m_parent.try_get_index(sym, sym_idx)) { return; } - - bool have_idx = sym_idx==m_idx; - - if( m_all ? (!have_idx) : have_idx ) { - m_found_what_needed = true; - } - - } - - bool all_have_idx() const - { - SASSERT(m_all); //we were looking for the queried property - return !m_found_what_needed; - } - - bool some_with_idx() const - { - SASSERT(!m_all); //we were looking for the queried property - return m_found_what_needed; - } - -private: - const sym_mux & m_parent; - bool m_all; - unsigned m_idx; - - /** - If we check whether all muxed symbols are of given index, we look for - counter-examples, checking whether form contains a muxed symbol of an index, - we look for symbol of index m_idx. - */ - bool m_found_what_needed; -}; - -bool sym_mux::contains(expr * e, unsigned idx) const -{ - formula_checker chck(*this, false, idx); - for_each_expr(chck, m_visited, e); - m_visited.reset(); - return chck.some_with_idx(); -} - -bool sym_mux::is_homogenous_formula(expr * e, unsigned idx) const -{ - formula_checker chck(*this, true, idx); - for_each_expr(chck, m_visited, e); - m_visited.reset(); - return chck.all_have_idx(); -} - -bool sym_mux::is_homogenous(const expr_ref_vector & vect, unsigned idx) const -{ - expr * const * begin = vect.c_ptr(); - expr * const * end = begin + vect.size(); - for(expr * const * it = begin; it!=end; it++) { - if(!is_homogenous_formula(*it, idx)) { - return false; - } - } - return true; -} - -class sym_mux::index_collector { - sym_mux const& m_parent; - svector m_indices; -public: - index_collector(sym_mux const& s): - m_parent(s) {} - - void operator()(expr * e) { - if (is_app(e)) { - func_decl * sym = to_app(e)->get_decl(); - unsigned idx; - if (m_parent.try_get_index(sym, idx)) { - SASSERT(idx > 0); - --idx; - if (m_indices.size() <= idx) { - m_indices.resize(idx+1, false); - } - m_indices[idx] = true; - } - } - } - - void extract(unsigned_vector& indices) { - for (unsigned i = 0; i < m_indices.size(); ++i) { - if (m_indices[i]) { - indices.push_back(i); - } - } - } -}; - - - -void sym_mux::collect_indices(expr* e, unsigned_vector& indices) const { - indices.reset(); - index_collector collector(*this); - for_each_expr(collector, m_visited, e); - m_visited.reset(); - collector.extract(indices); -} - -class sym_mux::variable_collector { - sym_mux const& m_parent; - vector >& m_vars; -public: - variable_collector(sym_mux const& s, vector >& vars): - m_parent(s), m_vars(vars) {} - - void operator()(expr * e) { - if (is_app(e)) { - func_decl * sym = to_app(e)->get_decl(); - unsigned idx; - if (m_parent.try_get_index(sym, idx)) { - SASSERT(idx > 0); - --idx; - if (m_vars.size() <= idx) { - m_vars.resize(idx+1, ptr_vector()); - } - m_vars[idx].push_back(to_app(e)); - } - } - } -}; - -void sym_mux::collect_variables(expr* e, vector >& vars) const { - vars.reset(); - variable_collector collector(*this, vars); - for_each_expr(collector, m_visited, e); - m_visited.reset(); -} - -class sym_mux::hmg_checker { - const sym_mux & m_parent; - - bool m_found_idx; - unsigned m_idx; - bool m_multiple_indexes; - -public: - hmg_checker(const sym_mux & parent) : - m_parent(parent), m_found_idx(false), m_multiple_indexes(false) - { - } - - void operator()(expr * e) - { - if(m_multiple_indexes || !is_app(e)) { return; } - - func_decl * sym = to_app(e)->get_decl(); - unsigned sym_idx; - if(!m_parent.try_get_index(sym, sym_idx)) { return; } - - if(!m_found_idx) { - m_found_idx = true; - m_idx = sym_idx; - return; - } - if(m_idx==sym_idx) { return; } - m_multiple_indexes = true; - } - - bool has_multiple_indexes() const - { - return m_multiple_indexes; - } -}; - -bool sym_mux::is_homogenous_formula(expr * e) const { - hmg_checker chck(*this); - for_each_expr(chck, m_visited, e); - m_visited.reset(); - return !chck.has_multiple_indexes(); -} - - -struct sym_mux::conv_rewriter_cfg : public default_rewriter_cfg -{ -private: - ast_manager & m; - sym_mux & m_parent; - unsigned m_from_idx; - unsigned m_to_idx; - bool m_homogenous; -public: - conv_rewriter_cfg(sym_mux & parent, unsigned from_idx, unsigned to_idx, bool homogenous) - : m(parent.get_manager()), - m_parent(parent), - m_from_idx(from_idx), - m_to_idx(to_idx), - m_homogenous(homogenous) {} - - bool get_subst(expr * s, expr * & t, proof * & t_pr) { - if(!is_app(s)) { return false; } - app * a = to_app(s); - func_decl * sym = a->get_decl(); - if(!m_parent.has_index(sym, m_from_idx)) { - (void) m_homogenous; - SASSERT(!m_homogenous || !m_parent.is_muxed(sym)); - return false; - } - func_decl * tgt = m_parent.conv(sym, m_from_idx, m_to_idx); - - t = m.mk_app(tgt, a->get_args()); - return true; - } -}; - -void sym_mux::conv_formula(expr * f, unsigned src_idx, unsigned tgt_idx, expr_ref & res, bool homogenous) -{ - if(src_idx==tgt_idx) { - res = f; - return; - } - conv_rewriter_cfg r_cfg(*this, src_idx, tgt_idx, homogenous); - rewriter_tpl rwr(m, false, r_cfg); - rwr(f, res); -} - -struct sym_mux::shifting_rewriter_cfg : public default_rewriter_cfg -{ -private: - ast_manager & m; - sym_mux & m_parent; - int m_shift; -public: - shifting_rewriter_cfg(sym_mux & parent, int shift) - : m(parent.get_manager()), - m_parent(parent), - m_shift(shift) {} - - bool get_subst(expr * s, expr * & t, proof * & t_pr) { - if(!is_app(s)) { return false; } - app * a = to_app(s); - func_decl * sym = a->get_decl(); - - unsigned idx; - if(!m_parent.try_get_index(sym, idx)) { - return false; - } - SASSERT(static_cast(idx)+m_shift>=0); - func_decl * tgt = m_parent.conv(sym, idx, idx+m_shift); - t = m.mk_app(tgt, a->get_args()); - return true; - } -}; - -void sym_mux::shift_formula(expr * f, int dist, expr_ref & res) -{ - if(dist==0) { - res = f; - return; - } - shifting_rewriter_cfg r_cfg(*this, dist); - rewriter_tpl rwr(m, false, r_cfg); - rwr(f, res); -} - -void sym_mux::conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, - expr_ref_vector & res) -{ - res.reset(); - expr * const * begin = vect.c_ptr(); - expr * const * end = begin + vect.size(); - for(expr * const * it = begin; it!=end; it++) { - expr_ref converted(m); - conv_formula(*it, src_idx, tgt_idx, converted); - res.push_back(converted); - } -} - -void sym_mux::filter_idx(expr_ref_vector & vect, unsigned idx) const { - unsigned i = 0; - while (i < vect.size()) { - expr* e = vect[i].get(); - if (contains(e, idx) && is_homogenous_formula(e, idx)) { - i++; - } - else { - // we don't allow mixing states inside vector elements - SASSERT(!contains(e, idx)); - vect[i] = vect.back(); - vect.pop_back(); - } - } -} - -void sym_mux::partition_o_idx( - expr_ref_vector const& lits, - expr_ref_vector& o_lits, - expr_ref_vector& other, unsigned idx) const { - - for (unsigned i = 0; i < lits.size(); ++i) { - if (contains(lits[i], idx) && is_homogenous_formula(lits[i], idx)) { - o_lits.push_back(lits[i]); - } - else { - other.push_back(lits[i]); - } - } -} - - - -class sym_mux::nonmodel_sym_checker { - const sym_mux & m_parent; - - bool m_found; -public: - nonmodel_sym_checker(const sym_mux & parent) : - m_parent(parent), m_found(false) { - } - - void operator()(expr * e) { - if(m_found || !is_app(e)) { return; } - - func_decl * sym = to_app(e)->get_decl(); - - if(m_parent.is_non_model_sym(sym)) { - m_found = true; - } - } - - bool found() const { - return m_found; - } -}; - -bool sym_mux::has_nonmodel_symbol(expr * e) const { - nonmodel_sym_checker chck(*this); - for_each_expr(chck, e); - return chck.found(); -} - -void sym_mux::filter_non_model_lits(expr_ref_vector & vect) const { - unsigned i = 0; - while (i < vect.size()) { - if (!has_nonmodel_symbol(vect[i].get())) { - i++; - } - else { - vect[i] = vect.back(); - vect.pop_back(); - } - } -} - -class sym_mux::decl_idx_comparator -{ - const sym_mux & m_parent; -public: - decl_idx_comparator(const sym_mux & parent) - : m_parent(parent) - { } - - bool operator()(func_decl * sym1, func_decl * sym2) - { - unsigned idx1, idx2; - if (!m_parent.try_get_index(sym1, idx1)) { idx1 = UINT_MAX; } - if (!m_parent.try_get_index(sym2, idx2)) { idx2 = UINT_MAX; } - - if (idx1 != idx2) { return idx1get_name(), sym2->get_name()); - } -}; - -std::string sym_mux::pp_model(const model_core & mdl) const { - decl_vector consts; - unsigned sz = mdl.get_num_constants(); - for (unsigned i = 0; i < sz; i++) { - func_decl * d = mdl.get_constant(i); - consts.push_back(d); - } - - std::sort(consts.begin(), consts.end(), decl_idx_comparator(*this)); - - std::stringstream res; - - decl_vector::iterator end = consts.end(); - for (decl_vector::iterator it = consts.begin(); it!=end; it++) { - func_decl * d = *it; - std::string name = d->get_name().str(); - const char * arrow = " -> "; - res << name << arrow; - unsigned indent = static_cast(name.length() + strlen(arrow)); - res << mk_pp(mdl.get_const_interp(d), m, indent) << "\n"; - - if (it+1 != end) { - unsigned idx1, idx2; - if (!try_get_index(*it, idx1)) { idx1 = UINT_MAX; } - if (!try_get_index(*(it+1), idx2)) { idx2 = UINT_MAX; } - if (idx1 != idx2) { res << "\n"; } - } - } - return res.str(); -} - - -#if 0 - -class sym_mux::index_renamer_cfg : public default_rewriter_cfg{ - const sym_mux & m_parent; - unsigned m_idx; - -public: - index_renamer_cfg(const sym_mux & p, unsigned idx) : m_parent(p), m_idx(idx) {} - - bool get_subst(expr * s, expr * & t, proof * & t_pr) { - if (!is_app(s)) return false; - app * a = to_app(s); - if (a->get_family_id() != null_family_id) { - return false; - } - func_decl * sym = a->get_decl(); - unsigned idx; - if(!m_parent.try_get_index(sym, idx)) { - return false; - } - if (m_idx == idx) { - return false; - } - ast_manager& m = m_parent.get_manager(); - symbol name = symbol((sym->get_name().str() + "!").c_str()); - func_decl * tgt = m.mk_func_decl(name, sym->get_arity(), sym->get_domain(), sym->get_range()); - t = m.mk_app(tgt, a->get_num_args(), a->get_args()); - return true; - } -}; - -#endif diff --git a/src/muz/pdr/pdr_sym_mux.h b/src/muz/pdr/pdr_sym_mux.h deleted file mode 100644 index 64a2878a9..000000000 --- a/src/muz/pdr/pdr_sym_mux.h +++ /dev/null @@ -1,247 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - sym_mux.h - -Abstract: - - A symbol multiplexer that helps with having multiple versions of each of a set of symbols. - -Author: - - Krystof Hoder (t-khoder) 2011-9-8. - -Revision History: - ---*/ - -#ifndef SYM_MUX_H_ -#define SYM_MUX_H_ - -#include "ast/ast.h" -#include "util/map.h" -#include "util/vector.h" -#include - -class model_core; - -namespace pdr { -class sym_mux -{ -public: - typedef ptr_vector app_vector; - typedef ptr_vector decl_vector; -private: - typedef obj_map sym2u; - typedef obj_map sym2dv; - typedef obj_map sym2sym; - typedef obj_map sym2pred; - typedef hashtable symbols; - - ast_manager & m; - mutable ast_ref_vector m_ref_holder; - mutable expr_mark m_visited; - - mutable unsigned m_next_sym_suffix_idx; - mutable symbols m_used_suffixes; - /** Here we have default suffixes for each of the variants */ - std::vector m_suffixes; - - - /** - Primary symbol is the 0-th variant. This member maps from primary symbol - to vector of all its variants (including the primary variant). - */ - sym2dv m_prim2all; - - /** - For each symbol contains its variant index - */ - mutable sym2u m_sym2idx; - /** - For each symbol contains its primary variant - */ - mutable sym2sym m_sym2prim; - - /** - Maps prefixes passed to the create_tuple to - the primary symbol created from it. - */ - sym2pred m_prefix2prim; - - /** - Maps pripary symbols to prefixes that were used to create them. - */ - sym2sym m_prim2prefix; - - decl_vector m_prim_preds; - - obj_hashtable m_non_model_syms; - - struct formula_checker; - struct conv_rewriter_cfg; - struct shifting_rewriter_cfg; - class decl_idx_comparator; - class hmg_checker; - class nonmodel_sym_checker; - class index_renamer_cfg; - class index_collector; - class variable_collector; - - std::string get_suffix(unsigned i); - void ensure_tuple_size(func_decl * prim, unsigned sz); - -public: - sym_mux(ast_manager & m); - - ast_manager & get_manager() const { return m; } - - bool is_muxed(func_decl * sym) const { return m_sym2idx.contains(sym); } - - bool try_get_index(func_decl * sym, unsigned & idx) const { - return m_sym2idx.find(sym,idx); - } - - bool has_index(func_decl * sym, unsigned idx) const { - unsigned actual_idx; - return try_get_index(sym, actual_idx) && idx==actual_idx; - } - - /** Return primary symbol. sym must be muxed. */ - func_decl * get_primary(func_decl * sym) const { - func_decl * prim; - TRUSTME(m_sym2prim.find(sym, prim)); - return prim; - } - - /** - Return primary symbol created from prefix, or 0 if the prefix was never used. - */ - func_decl * try_get_primary_by_prefix(func_decl* prefix) const { - func_decl * res; - if(!m_prefix2prim.find(prefix, res)) { - return nullptr; - } - return res; - } - - /** - Return symbol created from prefix, or 0 if the prefix was never used. - */ - func_decl * try_get_by_prefix(func_decl* prefix, unsigned idx) { - func_decl * prim = try_get_primary_by_prefix(prefix); - if(!prim) { - return nullptr; - } - return conv(prim, 0, idx); - } - - /** - Marks symbol as non-model which means it will not appear in models collected by - get_muxed_cube_from_model function. - This is to take care of auxiliary symbols introduced by the disjunction relations - to relativize lemmas coming from disjuncts. - */ - void mark_as_non_model(func_decl * sym) { - SASSERT(is_muxed(sym)); - m_non_model_syms.insert(get_primary(sym)); - } - - func_decl * get_or_create_symbol_by_prefix(func_decl* prefix, unsigned idx, - unsigned arity, sort * const * domain, sort * range); - - - - bool is_muxed_lit(expr * e, unsigned idx) const; - - bool is_non_model_sym(func_decl * s) const { - return is_muxed(s) && m_non_model_syms.contains(get_primary(s)); - } - - /** - Create a multiplexed tuple of propositional constants. - Symbols may be suplied in the tuple vector, - those beyond the size of the array and those with corresponding positions - assigned to zero will be created using prefix. - Tuple length must be at least one. - */ - void create_tuple(func_decl* prefix, unsigned arity, sort * const * domain, sort * range, - unsigned tuple_length, decl_vector & tuple); - - /** - Return true if the only multiplexed symbols which e contains are of index idx. - */ - bool is_homogenous_formula(expr * e, unsigned idx) const; - bool is_homogenous(const expr_ref_vector & vect, unsigned idx) const; - - /** - Return true if all multiplexed symbols which e contains are of one index. - */ - bool is_homogenous_formula(expr * e) const; - - /** - Return true if expression e contains a muxed symbol of index idx. - */ - bool contains(expr * e, unsigned idx) const; - - /** - Collect indices used in expression. - */ - void collect_indices(expr* e, unsigned_vector& indices) const; - - /** - Collect used variables of each index. - */ - void collect_variables(expr* e, vector >& vars) const; - - /** - Convert symbol sym which has to be of src_idx variant into variant tgt_idx. - */ - func_decl * conv(func_decl * sym, unsigned src_idx, unsigned tgt_idx); - - - /** - Convert src_idx symbols in formula f variant into tgt_idx. - If homogenous is true, formula cannot contain symbols of other variants. - */ - void conv_formula(expr * f, unsigned src_idx, unsigned tgt_idx, expr_ref & res, bool homogenous=true); - void conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, - expr_ref_vector & res); - - /** - Shifts the muxed symbols in f by dist. Dist can be negative, but it should never shift - symbol index to a negative value. - */ - void shift_formula(expr * f, int dist, expr_ref & res); - - /** - Remove from vect literals (atoms or negations of atoms) of symbols - that contain multiplexed symbols with indexes other than idx. - - Each of the literals can contain only symbols multiplexed with one index - (this trivially holds if the literals are propositional). - - Order of elements in vect may be modified by this function - */ - void filter_idx(expr_ref_vector & vect, unsigned idx) const; - - /** - Partition literals into o_literals and others. - */ - void partition_o_idx(expr_ref_vector const& lits, - expr_ref_vector& o_lits, - expr_ref_vector& other, unsigned idx) const; - - bool has_nonmodel_symbol(expr * e) const; - void filter_non_model_lits(expr_ref_vector & vect) const; - - func_decl * const * begin_prim_preds() const { return m_prim_preds.begin(); } - func_decl * const * end_prim_preds() const { return m_prim_preds.end(); } - - std::string pp_model(const model_core & mdl) const; -}; -} - -#endif diff --git a/src/muz/pdr/pdr_util.cpp b/src/muz/pdr/pdr_util.cpp deleted file mode 100644 index ad75ae799..000000000 --- a/src/muz/pdr/pdr_util.cpp +++ /dev/null @@ -1,508 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_util.cpp - -Abstract: - - Utility functions for PDR. - -Author: - - Krystof Hoder (t-khoder) 2011-8-19. - -Revision History: - - -Notes: - - ---*/ - -#include -#include "util/util.h" -#include "util/ref_vector.h" -#include "ast/array_decl_plugin.h" -#include "ast/ast_pp.h" -#include "ast/for_each_expr.h" -#include "ast/scoped_proof.h" -#include "ast/arith_decl_plugin.h" -#include "ast/rewriter/expr_replacer.h" -#include "ast/rewriter/bool_rewriter.h" -#include "ast/rewriter/poly_rewriter.h" -#include "ast/rewriter/poly_rewriter_def.h" -#include "ast/rewriter/arith_rewriter.h" -#include "ast/rewriter/rewriter.h" -#include "ast/rewriter/rewriter_def.h" -#include "smt/params/smt_params.h" -#include "model/model.h" -#include "muz/base/dl_util.h" -#include "muz/pdr/pdr_manager.h" -#include "muz/pdr/pdr_util.h" -#include "model/model_smt2_pp.h" - - - -namespace pdr { - - unsigned ceil_log2(unsigned u) { - if (u == 0) { return 0; } - unsigned pow2 = next_power_of_two(u); - return get_num_1bits(pow2-1); - } - - std::string pp_cube(const ptr_vector& model, ast_manager& m) { - return pp_cube(model.size(), model.c_ptr(), m); - } - - std::string pp_cube(const expr_ref_vector& model, ast_manager& m) { - return pp_cube(model.size(), model.c_ptr(), m); - } - - std::string pp_cube(const app_ref_vector& model, ast_manager& m) { - return pp_cube(model.size(), model.c_ptr(), m); - } - - std::string pp_cube(const app_vector& model, ast_manager& m) { - return pp_cube(model.size(), model.c_ptr(), m); - } - - std::string pp_cube(unsigned sz, app * const * lits, ast_manager& m) { - return pp_cube(sz, (expr * const *)(lits), m); - } - - std::string pp_cube(unsigned sz, expr * const * lits, ast_manager& m) { - std::stringstream res; - res << "("; - expr * const * end = lits+sz; - for (expr * const * it = lits; it!=end; it++) { - res << mk_pp(*it, m); - if (it+1!=end) { - res << ", "; - } - } - res << ")"; - return res.str(); - } - - void reduce_disequalities(model& model, unsigned threshold, expr_ref& fml) { - ast_manager& m = fml.get_manager(); - expr_ref_vector conjs(m); - flatten_and(fml, conjs); - obj_map diseqs; - expr* n, *lhs, *rhs; - for (unsigned i = 0; i < conjs.size(); ++i) { - if (m.is_not(conjs[i].get(), n) && - m.is_eq(n, lhs, rhs)) { - if (!m.is_value(rhs)) { - std::swap(lhs, rhs); - } - if (!m.is_value(rhs)) { - continue; - } - diseqs.insert_if_not_there2(lhs, 0)->get_data().m_value++; - } - } - expr_substitution sub(m); - - unsigned orig_size = conjs.size(); - unsigned num_deleted = 0; - expr_ref val(m), tmp(m); - proof_ref pr(m); - pr = m.mk_asserted(m.mk_true()); - obj_map::iterator it = diseqs.begin(); - obj_map::iterator end = diseqs.end(); - for (; it != end; ++it) { - if (it->m_value >= threshold) { - model.eval(it->m_key, val); - sub.insert(it->m_key, val, pr); - conjs.push_back(m.mk_eq(it->m_key, val)); - num_deleted += it->m_value; - } - } - if (orig_size < conjs.size()) { - scoped_ptr rep = mk_expr_simp_replacer(m); - rep->set_substitution(&sub); - for (unsigned i = 0; i < orig_size; ++i) { - tmp = conjs[i].get(); - (*rep)(tmp); - if (m.is_true(tmp)) { - conjs[i] = conjs.back(); - SASSERT(orig_size <= conjs.size()); - conjs.pop_back(); - SASSERT(orig_size <= 1 + conjs.size()); - if (i + 1 == orig_size) { - // no-op. - } - else if (orig_size <= conjs.size()) { - // no-op - } - else { - SASSERT(orig_size == 1 + conjs.size()); - --orig_size; - --i; - } - } - else { - conjs[i] = tmp; - } - } - IF_VERBOSE(2, verbose_stream() << "Deleted " << num_deleted << " disequalities " << conjs.size() << " conjuncts\n";); - } - fml = m.mk_and(conjs.size(), conjs.c_ptr()); - } - - class test_diff_logic { - ast_manager& m; - arith_util a; - bv_util bv; - bool m_is_dl; - bool m_test_for_utvpi; - - bool is_numeric(expr* e) const { - if (a.is_numeral(e)) { - return true; - } - expr* cond, *th, *el; - if (m.is_ite(e, cond, th, el)) { - return is_numeric(th) && is_numeric(el); - } - return false; - } - - bool is_arith_expr(expr *e) const { - return is_app(e) && a.get_family_id() == to_app(e)->get_family_id(); - } - - bool is_offset(expr* e) const { - if (a.is_numeral(e)) { - return true; - } - expr* cond, *th, *el, *e1, *e2; - if (m.is_ite(e, cond, th, el)) { - return is_offset(th) && is_offset(el); - } - // recognize offsets. - if (a.is_add(e, e1, e2)) { - if (is_numeric(e1)) { - return is_offset(e2); - } - if (is_numeric(e2)) { - return is_offset(e1); - } - return false; - } - if (m_test_for_utvpi) { - if (a.is_mul(e, e1, e2)) { - if (is_minus_one(e1)) { - return is_offset(e2); - } - if (is_minus_one(e2)) { - return is_offset(e1); - } - } - } - return !is_arith_expr(e); - } - - bool is_minus_one(expr const * e) const { - rational r; return a.is_numeral(e, r) && r.is_minus_one(); - } - - bool test_ineq(expr* e) const { - SASSERT(a.is_le(e) || a.is_ge(e) || m.is_eq(e)); - SASSERT(to_app(e)->get_num_args() == 2); - expr * lhs = to_app(e)->get_arg(0); - expr * rhs = to_app(e)->get_arg(1); - if (is_offset(lhs) && is_offset(rhs)) - return true; - if (!is_numeric(rhs)) - std::swap(lhs, rhs); - if (!is_numeric(rhs)) - return false; - // lhs can be 'x' or '(+ x (* -1 y))' - if (is_offset(lhs)) - return true; - expr* arg1, *arg2; - if (!a.is_add(lhs, arg1, arg2)) - return false; - // x - if (m_test_for_utvpi) { - return is_offset(arg1) && is_offset(arg2); - } - if (is_arith_expr(arg1)) - std::swap(arg1, arg2); - if (is_arith_expr(arg1)) - return false; - // arg2: (* -1 y) - expr* m1, *m2; - if (!a.is_mul(arg2, m1, m2)) - return false; - return is_minus_one(m1) && is_offset(m2); - } - - bool test_eq(expr* e) const { - expr* lhs = nullptr, *rhs = nullptr; - VERIFY(m.is_eq(e, lhs, rhs)); - if (!a.is_int_real(lhs)) { - return true; - } - if (a.is_numeral(lhs) || a.is_numeral(rhs)) { - return test_ineq(e); - } - return - test_term(lhs) && - test_term(rhs) && - !a.is_mul(lhs) && - !a.is_mul(rhs); - } - - bool test_term(expr* e) const { - if (m.is_bool(e)) { - return true; - } - if (a.is_numeral(e)) { - return true; - } - if (is_offset(e)) { - return true; - } - expr* lhs, *rhs; - if (a.is_add(e, lhs, rhs)) { - if (!a.is_numeral(lhs)) { - std::swap(lhs, rhs); - } - return a.is_numeral(lhs) && is_offset(rhs); - } - if (a.is_mul(e, lhs, rhs)) { - return is_minus_one(lhs) || is_minus_one(rhs); - } - return false; - } - - bool is_non_arith_or_basic(expr* e) { - if (!is_app(e)) { - return false; - } - family_id fid = to_app(e)->get_family_id(); - - if (fid == null_family_id && - !m.is_bool(e) && - to_app(e)->get_num_args() > 0) { - return true; - } - return - fid != m.get_basic_family_id() && - fid != null_family_id && - fid != a.get_family_id() && - fid != bv.get_family_id(); - } - - public: - test_diff_logic(ast_manager& m): m(m), a(m), bv(m), m_is_dl(true), m_test_for_utvpi(false) {} - - void test_for_utvpi() { m_test_for_utvpi = true; } - - void operator()(expr* e) { - if (!m_is_dl) { - return; - } - if (a.is_le(e) || a.is_ge(e)) { - m_is_dl = test_ineq(e); - } - else if (m.is_eq(e)) { - m_is_dl = test_eq(e); - } - else if (is_non_arith_or_basic(e)) { - m_is_dl = false; - } - else if (is_app(e)) { - app* a = to_app(e); - for (unsigned i = 0; m_is_dl && i < a->get_num_args(); ++i) { - m_is_dl = test_term(a->get_arg(i)); - } - } - - if (!m_is_dl) { - char const* msg = "non-diff: "; - if (m_test_for_utvpi) { - msg = "non-utvpi: "; - } - IF_VERBOSE(1, verbose_stream() << msg << mk_pp(e, m) << "\n";); - } - } - - bool is_dl() const { return m_is_dl; } - }; - - bool is_difference_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls) { - test_diff_logic test(m); - expr_fast_mark1 mark; - for (unsigned i = 0; i < num_fmls; ++i) { - quick_for_each_expr(test, mark, fmls[i]); - } - return test.is_dl(); - } - - bool is_utvpi_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls) { - test_diff_logic test(m); - test.test_for_utvpi(); - expr_fast_mark1 mark; - for (unsigned i = 0; i < num_fmls; ++i) { - quick_for_each_expr(test, mark, fmls[i]); - } - return test.is_dl(); - } - - class arith_normalizer : public poly_rewriter { - ast_manager& m; - arith_util m_util; - enum op_kind { LE, GE, EQ }; - public: - arith_normalizer(ast_manager& m, params_ref const& p = params_ref()): poly_rewriter(m, p), m(m), m_util(m) {} - - br_status mk_app_core(func_decl* f, unsigned num_args, expr* const* args, expr_ref& result) { - br_status st = BR_FAILED; - if (m.is_eq(f)) { - SASSERT(num_args == 2); return mk_eq_core(args[0], args[1], result); - } - - if (f->get_family_id() != get_fid()) { - return st; - } - switch (f->get_decl_kind()) { - case OP_NUM: st = BR_FAILED; break; - case OP_IRRATIONAL_ALGEBRAIC_NUM: st = BR_FAILED; break; - case OP_LE: SASSERT(num_args == 2); st = mk_le_core(args[0], args[1], result); break; - case OP_GE: SASSERT(num_args == 2); st = mk_ge_core(args[0], args[1], result); break; - case OP_LT: SASSERT(num_args == 2); st = mk_lt_core(args[0], args[1], result); break; - case OP_GT: SASSERT(num_args == 2); st = mk_gt_core(args[0], args[1], result); break; - default: st = BR_FAILED; break; - } - return st; - } - - private: - - br_status mk_eq_core(expr* arg1, expr* arg2, expr_ref& result) { - return mk_le_ge_eq_core(arg1, arg2, EQ, result); - } - br_status mk_le_core(expr* arg1, expr* arg2, expr_ref& result) { - return mk_le_ge_eq_core(arg1, arg2, LE, result); - } - br_status mk_ge_core(expr* arg1, expr* arg2, expr_ref& result) { - return mk_le_ge_eq_core(arg1, arg2, GE, result); - } - br_status mk_lt_core(expr* arg1, expr* arg2, expr_ref& result) { - result = m.mk_not(m_util.mk_ge(arg1, arg2)); - return BR_REWRITE2; - } - br_status mk_gt_core(expr* arg1, expr* arg2, expr_ref& result) { - result = m.mk_not(m_util.mk_le(arg1, arg2)); - return BR_REWRITE2; - } - - br_status mk_le_ge_eq_core(expr* arg1, expr* arg2, op_kind kind, expr_ref& result) { - if (m_util.is_real(arg1)) { - numeral g(0); - get_coeffs(arg1, g); - get_coeffs(arg2, g); - if (!g.is_one() && !g.is_zero()) { - SASSERT(g.is_pos()); - expr_ref new_arg1 = rdiv_polynomial(arg1, g); - expr_ref new_arg2 = rdiv_polynomial(arg2, g); - switch(kind) { - case LE: result = m_util.mk_le(new_arg1, new_arg2); return BR_DONE; - case GE: result = m_util.mk_ge(new_arg1, new_arg2); return BR_DONE; - case EQ: result = m_util.mk_eq(new_arg1, new_arg2); return BR_DONE; - } - } - } - return BR_FAILED; - } - - void update_coeff(numeral const& r, numeral& g) { - if (g.is_zero() || abs(r) < g) { - g = abs(r); - } - } - - void get_coeffs(expr* e, numeral& g) { - rational r; - unsigned sz; - expr* const* args = get_monomials(e, sz); - for (unsigned i = 0; i < sz; ++i) { - expr* arg = args[i]; - if (!m_util.is_numeral(arg, r)) { - get_power_product(arg, r); - } - update_coeff(r, g); - } - } - - expr_ref rdiv_polynomial(expr* e, numeral const& g) { - rational r; - SASSERT(g.is_pos()); - SASSERT(!g.is_one()); - expr_ref_vector monomes(m); - unsigned sz; - expr* const* args = get_monomials(e, sz); - for (unsigned i = 0; i < sz; ++i) { - expr* arg = args[i]; - if (m_util.is_numeral(arg, r)) { - monomes.push_back(m_util.mk_numeral(r/g, false)); - } - else { - expr* p = get_power_product(arg, r); - r /= g; - if (r.is_one()) { - monomes.push_back(p); - } - else { - monomes.push_back(m_util.mk_mul(m_util.mk_numeral(r, false), p)); - } - } - } - expr_ref result(m); - mk_add(monomes.size(), monomes.c_ptr(), result); - return result; - } - - }; - - - struct arith_normalizer_cfg: public default_rewriter_cfg { - arith_normalizer m_r; - bool rewrite_patterns() const { return false; } - br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { - return m_r.mk_app_core(f, num, args, result); - } - arith_normalizer_cfg(ast_manager & m, params_ref const & p):m_r(m,p) {} - }; - - class arith_normalizer_star : public rewriter_tpl { - arith_normalizer_cfg m_cfg; - public: - arith_normalizer_star(ast_manager & m, params_ref const & p): - rewriter_tpl(m, false, m_cfg), - m_cfg(m, p) {} - }; - - - void normalize_arithmetic(expr_ref& t) { - ast_manager& m = t.get_manager(); - scoped_no_proof _sp(m); - params_ref p; - arith_normalizer_star rw(m, p); - expr_ref tmp(m); - rw(t, tmp); - t = tmp; - } - -} - -template class rewriter_tpl; - - diff --git a/src/muz/pdr/pdr_util.h b/src/muz/pdr/pdr_util.h deleted file mode 100644 index 51f6978ec..000000000 --- a/src/muz/pdr/pdr_util.h +++ /dev/null @@ -1,81 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - pdr_util.h - -Abstract: - - Utility functions for PDR. - -Author: - - Krystof Hoder (t-khoder) 2011-8-19. - -Revision History: - ---*/ - -#ifndef PDR_UTIL_H_ -#define PDR_UTIL_H_ - -#include "ast/ast.h" -#include "ast/ast_pp.h" -#include "ast/ast_util.h" -#include "util/obj_hashtable.h" -#include "util/ref_vector.h" -#include "util/trace.h" -#include "util/vector.h" -#include "ast/arith_decl_plugin.h" -#include "ast/array_decl_plugin.h" -#include "ast/bv_decl_plugin.h" - - -class model; -class model_core; - -namespace pdr { - - /** - * Return the ceiling of base 2 logarithm of a number, - * or zero if the nmber is zero. - */ - unsigned ceil_log2(unsigned u); - - typedef ptr_vector app_vector; - typedef ptr_vector decl_vector; - typedef obj_hashtable func_decl_set; - - std::string pp_cube(const ptr_vector& model, ast_manager& manager); - std::string pp_cube(const expr_ref_vector& model, ast_manager& manager); - std::string pp_cube(const ptr_vector& model, ast_manager& manager); - std::string pp_cube(const app_ref_vector& model, ast_manager& manager); - std::string pp_cube(unsigned sz, app * const * lits, ast_manager& manager); - std::string pp_cube(unsigned sz, expr * const * lits, ast_manager& manager); - - - /** - \brief replace variables that are used in many disequalities by - an equality using the model. - - Assumption: the model satisfies the conjunctions. - */ - void reduce_disequalities(model& model, unsigned threshold, expr_ref& fml); - - /** - \brief normalize coefficients in polynomials so that least coefficient is 1. - */ - void normalize_arithmetic(expr_ref& t); - - - /** - \brief determine if formulas belong to difference logic or UTVPI fragment. - */ - bool is_difference_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls); - - bool is_utvpi_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls); - -} - -#endif diff --git a/src/muz/rel/dl_relation_manager.cpp b/src/muz/rel/dl_relation_manager.cpp index c9fc4f173..ff12e66c9 100644 --- a/src/muz/rel/dl_relation_manager.cpp +++ b/src/muz/rel/dl_relation_manager.cpp @@ -210,11 +210,9 @@ namespace datalog { if(m_favourite_relation_plugin && m_favourite_relation_plugin->can_handle_signature(s)) { return m_favourite_relation_plugin; } - relation_plugin_vector::iterator rpit = m_relation_plugins.begin(); - relation_plugin_vector::iterator rpend = m_relation_plugins.end(); - for(; rpit!=rpend; ++rpit) { - if((*rpit)->can_handle_signature(s)) { - return *rpit; + for (auto * r : m_relation_plugins) { + if (r->can_handle_signature(s)) { + return r; } } return nullptr; @@ -232,11 +230,9 @@ namespace datalog { if (m_favourite_table_plugin && m_favourite_table_plugin->can_handle_signature(t)) { return m_favourite_table_plugin; } - table_plugin_vector::iterator tpit = m_table_plugins.begin(); - table_plugin_vector::iterator tpend = m_table_plugins.end(); - for(; tpit!=tpend; ++tpit) { - if((*tpit)->can_handle_signature(t)) { - return *tpit; + for (auto * a : m_table_plugins) { + if (a->can_handle_signature(t)) { + return a; } } return nullptr; @@ -251,11 +247,9 @@ namespace datalog { } relation_plugin * relation_manager::get_relation_plugin(symbol const& s) { - relation_plugin_vector::iterator rpit = m_relation_plugins.begin(); - relation_plugin_vector::iterator rpend = m_relation_plugins.end(); - for(; rpit!=rpend; ++rpit) { - if((*rpit)->get_name()==s) { - return *rpit; + for (auto* r : m_relation_plugins) { + if(r->get_name() == s) { + return r; } } return nullptr; @@ -480,46 +474,37 @@ namespace datalog { std::string relation_manager::to_nice_string(const relation_signature & s) const { std::string res("["); bool first = true; - relation_signature::const_iterator it = s.begin(); - relation_signature::const_iterator end = s.end(); - for(; it!=end; ++it) { - if(first) { + for (auto const& sig : s) { + if (first) { first = false; } else { - res+=','; + res += ','; } - res+=to_nice_string(*it); + res += to_nice_string(sig); } - res+=']'; + res += ']'; return res; } void relation_manager::display(std::ostream & out) const { - relation_map::iterator it=m_relations.begin(); - relation_map::iterator end=m_relations.end(); - for(;it!=end;++it) { - out << "Table " << it->m_key->get_name() << "\n"; - it->m_value->display(out); + for (auto const& kv : m_relations) { + out << "Table " << kv.m_key->get_name() << "\n"; + kv.m_value->display(out); } } void relation_manager::display_relation_sizes(std::ostream & out) const { - relation_map::iterator it=m_relations.begin(); - relation_map::iterator end=m_relations.end(); - for(;it!=end;++it) { - out << "Relation " << it->m_key->get_name() << " has size " - << it->m_value->get_size_estimate_rows() << "\n"; + for (auto const& kv : m_relations) { + out << "Relation " << kv.m_key->get_name() << " has size " + << kv.m_value->get_size_estimate_rows() << "\n"; } } void relation_manager::display_output_tables(rule_set const& rules, std::ostream & out) const { const decl_set & output_preds = rules.get_output_predicates(); - decl_set::iterator it=output_preds.begin(); - decl_set::iterator end=output_preds.end(); - for(; it!=end; ++it) { - func_decl * pred = *it; + for (func_decl * pred : output_preds) { relation_base * rel = try_get_relation(pred); if (!rel) { out << "Tuples in " << pred->get_name() << ": \n"; @@ -1016,11 +1001,8 @@ namespace datalog { SASSERT(plugin.can_handle_signature(res_sign)); table_base * res = plugin.mk_empty(res_sign); - table_base::iterator it = t.begin(); - table_base::iterator end = t.end(); - - for(; it!=end; ++it) { - it->get_fact(m_row); + for (table_base::row_interface& a : t) { + a.get_fact(m_row); modify_fact(m_row); res->add_fact(m_row); } @@ -1191,13 +1173,10 @@ namespace datalog { table_fact m_row; public: void operator()(table_base & tgt, const table_base & src, table_base * delta) override { - table_base::iterator it = src.begin(); - table_base::iterator iend = src.end(); + for (table_base::row_interface& a : src) { + a.get_fact(m_row); - for(; it!=iend; ++it) { - it->get_fact(m_row); - - if(delta) { + if (delta) { if(!tgt.contains_fact(m_row)) { tgt.add_new_fact(m_row); delta->add_fact(m_row); @@ -1260,11 +1239,9 @@ namespace datalog { void operator()(table_base & r) { m_to_remove.reset(); unsigned sz = 0; - table_base::iterator it = r.begin(); - table_base::iterator iend = r.end(); - for(; it!=iend; ++it) { - it->get_fact(m_row); - if(should_remove(m_row)) { + for (table_base::row_interface& a : r) { + a.get_fact(m_row); + if (should_remove(m_row)) { m_to_remove.append(m_row.size(), m_row.c_ptr()); ++sz; } @@ -1456,7 +1433,7 @@ namespace datalog { m_removed_cols(removed_col_cnt, removed_cols) {} table_base* operator()(const table_base & tb) override { - table_base *t2 = tb.clone(); + scoped_rel t2 = tb.clone(); (*m_filter)(*t2); if (!m_project) { relation_manager & rmgr = t2->get_plugin().get_manager(); @@ -1572,8 +1549,7 @@ namespace datalog { TRACE("dl", tout << t1.get_plugin().get_name() << "\n";); scoped_rel aux = t1.clone(); (*m_filter)(*aux); - table_base * res = (*m_project)(*aux); - return res; + return (*m_project)(*aux); } }; @@ -1614,11 +1590,8 @@ namespace datalog { m_aux_table->reset(); } - - table_base::iterator it = t.begin(); - table_base::iterator iend = t.end(); - for(; it!=iend; ++it) { - it->get_fact(m_curr_fact); + for (table_base::row_interface& a : t) { + a.get_fact(m_curr_fact); if((*m_mapper)(m_curr_fact.c_ptr()+m_first_functional)) { m_aux_table->add_fact(m_curr_fact); } @@ -1699,13 +1672,10 @@ namespace datalog { SASSERT(plugin.can_handle_signature(res_sign)); table_base * res = plugin.mk_empty(res_sign); - table_base::iterator it = t.begin(); - table_base::iterator end = t.end(); - - - for(; it!=end; ++it) { + table_base::iterator it = t.begin(), end = t.end(); + for (; it != end; ++it) { mk_project(it); - if(!res->suggest_fact(m_former_row)) { + if (!res->suggest_fact(m_former_row)) { (*m_reducer)(m_former_row.c_ptr()+m_res_first_functional, m_row.c_ptr()+m_res_first_functional); res->ensure_fact(m_former_row); } diff --git a/src/muz/rel/dl_sparse_table.h b/src/muz/rel/dl_sparse_table.h index fbbbd12f8..43a967729 100644 --- a/src/muz/rel/dl_sparse_table.h +++ b/src/muz/rel/dl_sparse_table.h @@ -326,29 +326,29 @@ namespace datalog { public: unsigned m_offset; //!< in bits unsigned m_length; //!< in bits - - column_info(unsigned offset, unsigned length) \ - : m_big_offset(offset/8), - m_small_offset(offset%8), - m_mask( length==64 ? ULLONG_MAX : (static_cast(1)<(1)<(rec+m_big_offset); + const uint64_t * ptr = reinterpret_cast(rec + m_big_offset); uint64_t res = *ptr; - res>>=m_small_offset; - res&=m_mask; + res >>= m_small_offset; + res &= m_mask; return res; } void set(char * rec, table_element val) const { SASSERT( (val&~m_mask)==0 ); //the value fits into the column - uint64_t * ptr = reinterpret_cast(rec+m_big_offset); - *ptr&=m_write_mask; - *ptr|=val<(rec + m_big_offset); + *ptr &= m_write_mask; + *ptr |= val << m_small_offset; } unsigned next_ofs() const { return m_offset+m_length; } }; diff --git a/src/muz/rel/udoc_relation.cpp b/src/muz/rel/udoc_relation.cpp index 2e9ab693c..ea292a29e 100644 --- a/src/muz/rel/udoc_relation.cpp +++ b/src/muz/rel/udoc_relation.cpp @@ -869,7 +869,7 @@ namespace datalog { dm.set(*d, idx, BIT_1); result.intersect(dm, *d); } - else if ((m.is_eq(g, e1, e2) || m.is_iff(g, e1, e2)) && m.is_bool(e1)) { + else if (m.is_iff(g, e1, e2)) { udoc diff1, diff2; diff1.push_back(dm.allocateX()); diff2.push_back(dm.allocateX()); diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index 37bc7f352..43fffd9fb 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -8,18 +8,25 @@ z3_add_component(spacer spacer_generalizers.cpp spacer_manager.cpp spacer_prop_solver.cpp - spacer_smt_context_manager.cpp spacer_sym_mux.cpp spacer_util.cpp - spacer_itp_solver.cpp - spacer_virtual_solver.cpp + spacer_iuc_solver.cpp spacer_legacy_mbp.cpp + spacer_proof_utils.cpp spacer_unsat_core_learner.cpp spacer_unsat_core_plugin.cpp spacer_matrix.cpp spacer_antiunify.cpp spacer_mev_array.cpp spacer_qe_project.cpp + spacer_sem_matcher.cpp + spacer_quant_generalizer.cpp + spacer_callback.cpp + spacer_json.cpp + spacer_iuc_proof.cpp + spacer_mbc.cpp + spacer_pdr.cpp + spacer_sat_answer.cpp COMPONENT_DEPENDENCIES arith_tactics core_tactics diff --git a/src/muz/spacer/spacer_antiunify.cpp b/src/muz/spacer/spacer_antiunify.cpp index 6baf9e93d..0edfb6598 100644 --- a/src/muz/spacer/spacer_antiunify.cpp +++ b/src/muz/spacer/spacer_antiunify.cpp @@ -28,6 +28,7 @@ Revision History: namespace spacer { + // Abstracts numeric values by variables struct var_abs_rewriter : public default_rewriter_cfg { ast_manager &m; @@ -56,8 +57,8 @@ struct var_abs_rewriter : public default_rewriter_cfg { { bool contains_const_child = false; app* a = to_app(t); - for (unsigned i=0, sz = a->get_num_args(); i < sz; ++i) { - if (m_util.is_numeral(a->get_arg(i))) { + for (expr * arg : *a) { + if (m_util.is_numeral(arg)) { contains_const_child = true; } } @@ -102,190 +103,73 @@ struct var_abs_rewriter : public default_rewriter_cfg { }; -/* -* construct m_g, which is a generalization of t, where every constant -* is replaced by a variable for any variable in m_g, remember the -* substitution to get back t and save it in m_substitutions -*/ -anti_unifier::anti_unifier(expr* t, ast_manager& man) : m(man), m_pinned(m), m_g(m) -{ - m_pinned.push_back(t); - obj_map substitution; +anti_unifier::anti_unifier(ast_manager &manager) : m(manager), m_pinned(m) {} - var_abs_rewriter var_abs_cfg(m, substitution); - rewriter_tpl var_abs_rw (m, false, var_abs_cfg); - var_abs_rw (t, m_g); - - m_substitutions.push_back(substitution); //TODO: refactor into vector, remove k +void anti_unifier::reset() { + m_subs.reset(); + m_cache.reset(); + m_todo.reset(); + m_pinned.reset(); } -/* traverses m_g and t in parallel. if they only differ in constants - * (i.e. m_g contains a variable, where t contains a constant), then - * add the substitutions, which need to be applied to m_g to get t, to - * m_substitutions. -*/ -bool anti_unifier::add_term(expr* t) { - m_pinned.push_back(t); +void anti_unifier::operator()(expr *e1, expr *e2, expr_ref &res, + substitution &s1, substitution &s2) { - ptr_vector todo; - ptr_vector todo2; - todo.push_back(m_g); - todo2.push_back(t); + reset(); + if (e1 == e2) {res = e1; s1.reset(); s2.reset(); return;} - ast_mark visited; + m_todo.push_back(expr_pair(e1, e2)); + while (!m_todo.empty()) { + const expr_pair &p = m_todo.back(); + SASSERT(is_app(p.first)); + SASSERT(is_app(p.second)); - arith_util util(m); + app * n1 = to_app(p.first); + app * n2 = to_app(p.second); - obj_map substitution; - - while (!todo.empty()) { - expr* current = todo.back(); - todo.pop_back(); - expr* current2 = todo2.back(); - todo2.pop_back(); - - if (!visited.is_marked(current)) { - visited.mark(current, true); - - if (is_var(current)) { - // TODO: for now we don't allow variables in the terms we want to antiunify - SASSERT(m_substitutions[0].contains(current)); - if (util.is_numeral(current2)) { - substitution.insert(current, current2); - } - else {return false;} - } - else { - SASSERT(is_app(current)); - - if (is_app(current2) && - to_app(current)->get_decl() == to_app(current2)->get_decl() && - to_app(current)->get_num_args() == to_app(current2)->get_num_args()) { - // TODO: what to do for numerals here? E.g. if we - // have 1 and 2, do they have the same decl or are - // the decls already different? - SASSERT (!util.is_numeral(current) || current == current2); - for (unsigned i = 0, num_args = to_app(current)->get_num_args(); - i < num_args; ++i) { - todo.push_back(to_app(current)->get_arg(i)); - todo2.push_back(to_app(current2)->get_arg(i)); - } - } - else { - return false; - } - } - } - } - - // we now know that the terms can be anti-unified, so add the cached substitution - m_substitutions.push_back(substitution); - return true; -} - -/* -* returns m_g, where additionally any variable, which has only equal -* substitutions, is substituted with that substitution -*/ -void anti_unifier::finalize() { - ptr_vector todo; - todo.push_back(m_g); - - ast_mark visited; - - obj_map generalization; - - arith_util util(m); - - // post-order traversel which ignores constants and handles them - // directly when the enclosing term of the constant is handled - while (!todo.empty()) { - expr* current = todo.back(); - SASSERT(is_app(current)); - - // if we haven't already visited current - if (!visited.is_marked(current)) { - bool existsUnvisitedParent = false; - - for (unsigned i = 0, sz = to_app(current)->get_num_args(); i < sz; ++i) { - expr* argument = to_app(current)->get_arg(i); - - if (!is_var(argument)) { - SASSERT(is_app(argument)); - // if we haven't visited the current parent yet - if(!visited.is_marked(argument)) { - // add it to the stack - todo.push_back(argument); - existsUnvisitedParent = true; - } - } - } - - // if we already visited all parents, we can visit current too - if (!existsUnvisitedParent) { - visited.mark(current, true); - todo.pop_back(); - - ptr_buffer arg_list; - for (unsigned i = 0, num_args = to_app(current)->get_num_args(); - i < num_args; ++i) { - expr* argument = to_app(current)->get_arg(i); - - if (is_var(argument)) { - // compute whether there are different - // substitutions for argument - bool containsDifferentSubstitutions = false; - - for (unsigned i=0, sz = m_substitutions.size(); i+1 < sz; ++i) { - SASSERT(m_substitutions[i].contains(argument)); - SASSERT(m_substitutions[i+1].contains(argument)); - - // TODO: how to check equality? - if (m_substitutions[i][argument] != - m_substitutions[i+1][argument]) - { - containsDifferentSubstitutions = true; - break; - } - } - - // if yes, use the variable - if (containsDifferentSubstitutions) { - arg_list.push_back(argument); - } - // otherwise use the concrete value instead - // and remove the substitutions - else - { - arg_list.push_back(m_substitutions[0][argument]); - - for (unsigned i=0, sz = m_substitutions.size(); i < sz; ++i) { - SASSERT(m_substitutions[i].contains(argument)); - m_substitutions[i].remove(argument); - } - } - } - else { - SASSERT(generalization.contains(argument)); - arg_list.push_back(generalization[argument]); - } - } - - SASSERT(to_app(current)->get_num_args() == arg_list.size()); - expr_ref application(m.mk_app(to_app(current)->get_decl(), - to_app(current)->get_num_args(), - arg_list.c_ptr()), m); - m_pinned.push_back(application); - generalization.insert(current, application); - } + unsigned num_arg1 = n1->get_num_args(); + unsigned num_arg2 = n2->get_num_args(); + if (n1->get_decl() != n2->get_decl() || num_arg1 != num_arg2) { + expr_ref v(m); + v = m.mk_var(m_subs.size(), get_sort(n1)); + m_pinned.push_back(v); + m_subs.push_back(expr_pair(n1, n2)); + m_cache.insert(n1, n2, v); } else { - todo.pop_back(); + expr *tmp; + unsigned todo_sz = m_todo.size(); + ptr_buffer kids; + for (unsigned i = 0; i < num_arg1; ++i) { + expr *arg1 = n1->get_arg(i); + expr *arg2 = n2->get_arg(i); + if (arg1 == arg2) {kids.push_back(arg1);} + else if (m_cache.find(arg1, arg2, tmp)) {kids.push_back(tmp);} + else {m_todo.push_back(expr_pair(arg1, arg2));} + } + if (m_todo.size() > todo_sz) {continue;} + + expr_ref u(m); + u = m.mk_app(n1->get_decl(), kids.size(), kids.c_ptr()); + m_pinned.push_back(u); + m_cache.insert(n1, n2, u); } } - m_g = generalization[m_g]; + expr *r; + VERIFY(m_cache.find(e1, e2, r)); + res = r; + + // create substitutions + s1.reserve(2, m_subs.size()); + s2.reserve(2, m_subs.size()); + + for (unsigned i = 0, sz = m_subs.size(); i < sz; ++i) { + expr_pair p = m_subs.get(i); + s1.insert(i, 0, expr_offset(p.first, 1)); + s2.insert(i, 0, expr_offset(p.second, 1)); + } } @@ -318,6 +202,8 @@ public: */ bool naive_convex_closure::compute_closure(anti_unifier& au, ast_manager& m, expr_ref& result) { + NOT_IMPLEMENTED_YET(); +#if 0 arith_util util(m); SASSERT(au.get_num_substitutions() > 0); @@ -411,6 +297,7 @@ bool naive_convex_closure::compute_closure(anti_unifier& au, ast_manager& m, result = expr_ref(m.mk_exists(vars.size(), sorts.c_ptr(), names.c_ptr(), body),m); return true; +#endif } bool naive_convex_closure::get_range(vector& v, @@ -453,7 +340,83 @@ void naive_convex_closure::substitute_vars_by_const(ast_manager& m, expr* t, subs_rw (t, res); } + +/// Construct a pattern by abstracting all numbers by variables +struct mk_num_pat_rewriter : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; + + // -- mark already seen expressions + ast_mark m_seen; + // -- true if the expression is known to have a number as a sub-expression + ast_mark m_has_num; + // -- expressions created during the transformation + expr_ref_vector m_pinned; + // -- map from introduced variables to expressions they replace + app_ref_vector &m_subs; + + + // -- stack of expressions being processed to have access to expressions + // -- before rewriting + ptr_buffer m_stack; + + mk_num_pat_rewriter (ast_manager &manager, app_ref_vector& subs) : + m(manager), m_arith(m), m_pinned(m), m_subs(subs) {} + + bool pre_visit(expr * t) { + // -- don't touch multiplication + if (m_arith.is_mul(t)) return false; + + bool r = (!m_seen.is_marked(t) || m_has_num.is_marked(t)); + if (r) {m_stack.push_back (t);} + return r; + } + + + br_status reduce_app (func_decl * f, unsigned num, expr * const * args, + expr_ref & result, proof_ref & result_pr) { + expr *s; + s = m_stack.back(); + m_stack.pop_back(); + if (is_app(s)) { + app *a = to_app(s); + for (unsigned i = 0, sz = a->get_num_args(); i < sz; ++i) { + if (m_has_num.is_marked(a->get_arg(i))) { + m_has_num.mark(a, true); + break; + } + } + } + return BR_FAILED; + } + + bool cache_all_results() const { return false; } + bool cache_results() const { return false; } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if (m_arith.is_numeral(s)) { + t = m.mk_var(m_subs.size(), m.get_sort(s)); + m_pinned.push_back(t); + m_subs.push_back(to_app(s)); + + m_has_num.mark(t, true); + m_seen.mark(t, true); + return true; + } + return false; + } + +}; + +void mk_num_pat(expr *e, expr_ref &result, app_ref_vector &subs) { + SASSERT(subs.empty()); + mk_num_pat_rewriter rw_cfg(result.m(), subs); + rewriter_tpl rw(result.m(), false, rw_cfg); + rw(e, result); +} + } template class rewriter_tpl; template class rewriter_tpl; +template class rewriter_tpl; diff --git a/src/muz/spacer/spacer_antiunify.h b/src/muz/spacer/spacer_antiunify.h index 2b86f67ec..af7f6f825 100644 --- a/src/muz/spacer/spacer_antiunify.h +++ b/src/muz/spacer/spacer_antiunify.h @@ -22,32 +22,36 @@ Revision History: #define _SPACER_ANTIUNIFY_H_ #include "ast/ast.h" - +#include "ast/substitution/substitution.h" +#include "util/obj_pair_hashtable.h" namespace spacer { +/** +\brief Anti-unifier for ground expressions +*/ class anti_unifier { -public: - anti_unifier(expr* t, ast_manager& m); - ~anti_unifier() {} + typedef std::pair expr_pair; + typedef pair_hash, obj_ptr_hash > expr_pair_hash; + typedef obj_pair_map cache_ty; - bool add_term(expr* t); - void finalize(); - - expr* get_generalization() {return m_g;} - unsigned get_num_substitutions() {return m_substitutions.size();} - obj_map get_substitution(unsigned index){ - SASSERT(index < m_substitutions.size()); - return m_substitutions[index]; - } - -private: - ast_manager& m; - // tracking all created expressions + ast_manager &m; expr_ref_vector m_pinned; - expr_ref m_g; + svector m_todo; + cache_ty m_cache; + svector m_subs; - vector> m_substitutions; +public: + anti_unifier(ast_manager& m); + + void reset(); + + /** + \brief Computes anti-unifier of two ground expressions. Returns + the anti-unifier and the corresponding substitutions + */ + void operator() (expr *e1, expr *e2, expr_ref &res, + substitution &s1, substitution &s2); }; class naive_convex_closure @@ -63,5 +67,8 @@ private: expr_ref& res); }; +/// Abstracts numbers in the given ground expression by variables +/// Returns the created pattern and the corresponding substitution. +void mk_num_pat(expr *e, expr_ref &result, app_ref_vector &subs); } #endif diff --git a/src/muz/spacer/spacer_callback.cpp b/src/muz/spacer/spacer_callback.cpp new file mode 100644 index 000000000..9c7c88ad9 --- /dev/null +++ b/src/muz/spacer/spacer_callback.cpp @@ -0,0 +1,38 @@ +/**++ +Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti + +Module Name: + + spacer_callback.cpp + +Abstract: + + SPACER plugin for handling events + +Author: + + Matteo Marescotti + +Notes: + +--*/ + +#include "spacer_callback.h" +#include "muz/spacer/spacer_context.h" + + +namespace spacer { + + void user_callback::new_lemma_eh(expr *lemma, unsigned level) { + m_new_lemma_eh(m_state, lemma, level); + } + + void user_callback::predecessor_eh() { + m_predecessor_eh(m_state); + } + + void user_callback::unfold_eh() { + m_unfold_eh(m_state); + } + +} \ No newline at end of file diff --git a/src/muz/spacer/spacer_callback.h b/src/muz/spacer/spacer_callback.h new file mode 100644 index 000000000..35805c7bc --- /dev/null +++ b/src/muz/spacer/spacer_callback.h @@ -0,0 +1,65 @@ +/**++ +Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti + +Module Name: + + spacer_callback.h + +Abstract: + + SPACER plugin for handling events + +Author: + + Matteo Marescotti + +Notes: + +--*/ + +#ifndef _SPACER_CALLBACK_H_ +#define _SPACER_CALLBACK_H_ + +#include "muz/spacer/spacer_context.h" +#include "muz/base/dl_engine_base.h" + + +namespace spacer { + + class user_callback : public spacer_callback { + private: + void *m_state; + const datalog::t_new_lemma_eh m_new_lemma_eh; + const datalog::t_predecessor_eh m_predecessor_eh; + const datalog::t_unfold_eh m_unfold_eh; + + public: + user_callback(context &context, + void *state, + const datalog::t_new_lemma_eh new_lemma_eh, + const datalog::t_predecessor_eh predecessor_eh, + const datalog::t_unfold_eh unfold_eh) : + spacer_callback(context), + m_state(state), + m_new_lemma_eh(new_lemma_eh), + m_predecessor_eh(predecessor_eh), + m_unfold_eh(unfold_eh) {} + + inline bool new_lemma() override { return m_new_lemma_eh != nullptr; } + + void new_lemma_eh(expr *lemma, unsigned level) override; + + inline bool predecessor() override { return m_predecessor_eh != nullptr; } + + void predecessor_eh() override; + + inline bool unfold() override { return m_unfold_eh != nullptr; } + + void unfold_eh() override; + + }; + +} + + +#endif //_SPACER_CALLBACK_H_ diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 7995f030c..f3ceee9c8 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -51,1066 +51,419 @@ Notes: #include "ast/rewriter/expr_safe_replace.h" #include "ast/expr_abstract.h" +#include "smt/smt_solver.h" + +#include "muz/spacer/spacer_sat_answer.h" + namespace spacer { +/// pob -- proof obligation +pob::pob (pob* parent, pred_transformer& pt, + unsigned level, unsigned depth, bool add_to_parent): + m_ref_count (0), + m_parent (parent), m_pt (pt), + m_post (m_pt.get_ast_manager ()), + m_binding(m_pt.get_ast_manager()), + m_new_post (m_pt.get_ast_manager ()), + m_level (level), m_depth (depth), + m_open (true), m_use_farkas (true), m_weakness(0), + m_blocked_lvl(0) { + if (add_to_parent && m_parent) { + m_parent->add_child(*this); + } +} + +void pob::set_post(expr* post) { + app_ref_vector empty_binding(get_ast_manager()); + set_post(post, empty_binding); +} + +void pob::set_post(expr* post, app_ref_vector const &binding) { + normalize(post, m_post, + m_pt.get_context().simplify_pob(), + m_pt.get_context().use_euf_gen()); + + m_binding.reset(); + if (!binding.empty()) {m_binding.append(binding);} +} + +void pob::inherit(pob const &p) { + SASSERT(m_parent == p.m_parent); + SASSERT(&m_pt == &p.m_pt); + SASSERT(m_post == p.m_post); + SASSERT(!m_new_post); + + m_binding.reset(); + m_binding.append(p.m_binding); + + m_level = p.m_level; + m_depth = p.m_depth; + m_open = p.m_open; + m_use_farkas = p.m_use_farkas; + m_weakness = p.m_weakness; + + m_derivation = nullptr; +} + +void pob::clean () { + if (m_new_post) { + m_post = m_new_post; + m_new_post.reset(); + } +} + +void pob::close () { + if (!m_open) { return; } + + reset (); + m_open = false; + for (unsigned i = 0, sz = m_kids.size (); i < sz; ++i) + { m_kids [i]->close(); } +} + +void pob::get_skolems(app_ref_vector &v) { + for (unsigned i = 0, sz = m_binding.size(); i < sz; ++i) { + expr* e; + e = m_binding.get(i); + v.push_back (mk_zk_const (get_ast_manager(), i, get_sort(e))); + } +} + + + // ---------------- -// pred_tansformer +// pob_queue -pred_transformer::pred_transformer(context& ctx, manager& pm, func_decl* head): - pm(pm), m(pm.get_manager()), - ctx(ctx), m_head(head, m), - m_sig(m), m_solver(pm, ctx.get_params(), head->get_name()), - m_reach_ctx (pm.mk_fresh3 ()), - m_pobs(*this), - m_frames(*this), - m_reach_facts(), m_rf_init_sz(0), - m_transition(m), m_initial_state(m), m_extend_lit(m), - m_all_init(false), - m_reach_case_vars(m) +pob* pob_queue::top () { - init_sig (); - app_ref v(m); - std::stringstream name; - name << m_head->get_name () << "_ext0"; - v = m.mk_const (symbol(name.str().c_str()), m.mk_bool_sort()); - m_extend_lit = m.mk_not (m.mk_const (pm.get_n_pred (v->get_decl ()))); + /// nothing in the queue + if (m_obligations.empty()) { return nullptr; } + /// top queue element is above max level + if (m_obligations.top()->level() > m_max_level) { return nullptr; } + /// top queue element is at the max level, but at a higher than base depth + if (m_obligations.top ()->level () == m_max_level && + m_obligations.top()->depth() > m_min_depth) { return nullptr; } + + /// there is something good in the queue + return m_obligations.top ().get (); } -pred_transformer::~pred_transformer() +void pob_queue::set_root(pob& root) { - rule2inst::iterator it2 = m_rule2inst.begin(), end2 = m_rule2inst.end(); - for (; it2 != end2; ++it2) { - dealloc(it2->m_value); - } - rule2expr::iterator it3 = m_rule2transition.begin(), end3 = m_rule2transition.end(); - for (; it3 != end3; ++it3) { - m.dec_ref(it3->m_value); - } + m_root = &root; + m_max_level = root.level (); + m_min_depth = root.depth (); + reset(); } -std::ostream& pred_transformer::display(std::ostream& out) const +pob_queue::~pob_queue() {} + +void pob_queue::reset() { - if (!rules().empty()) { out << "rules\n"; } - datalog::rule_manager& rm = ctx.get_datalog_context().get_rule_manager(); - for (unsigned i = 0; i < rules().size(); ++i) { - rm.display_smt2(*rules()[i], out) << "\n"; - } - out << "transition\n" << mk_pp(transition(), m) << "\n"; - return out; + while (!m_obligations.empty()) { m_obligations.pop(); } + if (m_root) { m_obligations.push(m_root); } } -void pred_transformer::collect_statistics(statistics& st) const -{ - m_solver.collect_statistics(st); - st.update("SPACER num propagations", m_stats.m_num_propagations); - st.update("SPACER num properties", m_frames.lemma_size ()); - st.update("SPACER num invariants", m_stats.m_num_invariants); - - st.update ("time.spacer.init_rules.pt.init", m_initialize_watch.get_seconds ()); - st.update ("time.spacer.solve.pt.must_reachable", - m_must_reachable_watch.get_seconds ()); +void pob_queue::push(pob &n) { + TRACE("pob_queue", + tout << "pob_queue::push(" << n.post()->get_id() << ")\n";); + m_obligations.push (&n); + n.get_context().new_pob_eh(&n); } -void pred_transformer::reset_statistics() -{ - m_solver.reset_statistics(); - //m_reachable.reset_statistics(); - m_stats.reset(); - m_initialize_watch.reset (); - m_must_reachable_watch.reset (); +// ---------------- +// derivation + +derivation::derivation (pob& parent, datalog::rule const& rule, + expr *trans, app_ref_vector const &evars) : + m_parent (parent), + m_rule (rule), + m_premises (), + m_active (0), + m_trans (trans, m_parent.get_ast_manager ()), + m_evars (evars) {} + + + +void derivation::add_premise (pred_transformer &pt, + unsigned oidx, + expr* summary, + bool must, + const ptr_vector *aux_vars) +{m_premises.push_back (premise (pt, oidx, summary, must, aux_vars));} + + + +pob *derivation::create_first_child (model &mdl) { + if (m_premises.empty()) { return nullptr; } + m_active = 0; + return create_next_child(mdl); } -void pred_transformer::init_sig() -{ - for (unsigned i = 0; i < m_head->get_arity(); ++i) { - sort * arg_sort = m_head->get_domain(i); - std::stringstream name_stm; - name_stm << m_head->get_name() << '_' << i; - func_decl_ref stm(m); - stm = m.mk_func_decl(symbol(name_stm.str().c_str()), 0, (sort*const*)nullptr, arg_sort); - m_sig.push_back(pm.get_o_pred(stm, 0)); - } -} +void derivation::exist_skolemize(expr* fml, app_ref_vector &vars, expr_ref &res) { + ast_manager &m = get_ast_manager(); + if (vars.empty()) {res = fml; return;} + if (m.is_true(fml) || m.is_false(fml)) {res = fml; return;} -void pred_transformer::ensure_level(unsigned level) -{ - if (is_infty_level(level)) { return; } - - while (m_frames.size() <= level) { - m_frames.add_frame (); - m_solver.add_level (); - } -} - -bool pred_transformer::is_must_reachable(expr* state, model_ref* model) -{ - scoped_watch _t_(m_must_reachable_watch); - SASSERT (state); - // XXX This seems to mis-handle the case when state is - // reachable using the init rule of the current transformer - if (m_reach_facts.empty()) { return false; } - - m_reach_ctx->push (); - m_reach_ctx->assert_expr (state); - m_reach_ctx->assert_expr (m.mk_not (m_reach_case_vars.back ())); - lbool res = m_reach_ctx->check_sat (0, nullptr); - if (model) { m_reach_ctx->get_model(*model); } - m_reach_ctx->pop (1); - return (res == l_true); -} - - - - -reach_fact* pred_transformer::get_used_reach_fact (model_evaluator_util& mev, - bool all) -{ - expr_ref v (m); - - for (unsigned i = all ? 0 : m_rf_init_sz, sz = m_reach_case_vars.size (); - i < sz; i++) { - VERIFY (mev.eval (m_reach_case_vars.get (i), v, false)); - if (m.is_false (v)) { - return m_reach_facts.get (i); - } - } - - UNREACHABLE (); - return nullptr; -} - -reach_fact *pred_transformer::get_used_origin_reach_fact (model_evaluator_util& mev, - unsigned oidx) -{ - expr_ref b(m), v(m); - reach_fact *res = nullptr; - - for (unsigned i = 0, sz = m_reach_case_vars.size (); i < sz; i++) { - pm.formula_n2o (m_reach_case_vars.get (i), v, oidx); - VERIFY(mev.eval (v, b, false)); - - if (m.is_false (b)) { - res = m_reach_facts.get (i); - break; - } - } - SASSERT (res); - return res; -} - -datalog::rule const* pred_transformer::find_rule(model &model, - bool& is_concrete, - vector& reach_pred_used, - unsigned& num_reuse_reach) -{ - typedef obj_map tag2rule; - TRACE ("spacer_verbose", - datalog::rule_manager& rm = ctx.get_datalog_context().get_rule_manager(); - tag2rule::iterator it = m_tag2rule.begin(); - tag2rule::iterator end = m_tag2rule.end(); - for (; it != end; ++it) { - expr* pred = it->m_key; - tout << mk_pp(pred, m) << ":\n"; - if (it->m_value) { rm.display_smt2(*(it->m_value), tout) << "\n"; } - } - ); - - // find a rule whose tag is true in the model; - // prefer a rule where the model intersects with reach facts of all predecessors; - // also find how many predecessors' reach facts are true in the model - expr_ref vl(m); - datalog::rule const* r = ((datalog::rule*)nullptr); - tag2rule::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); - for (; it != end; ++it) { - expr* tag = it->m_key; - if (model.eval(to_app(tag)->get_decl(), vl) && m.is_true(vl)) { - r = it->m_value; - is_concrete = true; - num_reuse_reach = 0; - reach_pred_used.reset (); - unsigned tail_sz = r->get_uninterpreted_tail_size (); - for (unsigned i = 0; i < tail_sz; i++) { - bool used = false; - func_decl* d = r->get_tail(i)->get_decl(); - pred_transformer const& pt = ctx.get_pred_transformer (d); - if (!pt.has_reach_facts()) { is_concrete = false; } - else { - expr_ref v(m); - pm.formula_n2o (pt.get_last_reach_case_var (), v, i); - model.eval (to_app (v.get ())->get_decl (), vl); - used = m.is_false (vl); - is_concrete = is_concrete && used; - } - - reach_pred_used.push_back (used); - if (used) { num_reuse_reach++; } - } - if (is_concrete) { break; } - } - } - // SASSERT (r); - // reached by a reachability fact - if (!r) { is_concrete = true; } - return r; -} - -void pred_transformer::find_predecessors(datalog::rule const& r, ptr_vector& preds) const -{ - preds.reset(); - unsigned tail_sz = r.get_uninterpreted_tail_size(); - for (unsigned ti = 0; ti < tail_sz; ti++) { - preds.push_back(r.get_tail(ti)->get_decl()); - } -} - -void pred_transformer::find_predecessors(vector >& preds) const -{ - preds.reset(); - obj_map::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); - for (; it != end; it++) { - datalog::rule const& r = *it->m_value; - unsigned tail_sz = r.get_uninterpreted_tail_size(); - for (unsigned ti = 0; ti < tail_sz; ti++) { - preds.push_back(std::make_pair (r.get_tail(ti)->get_decl(), ti)); - } - } -} - - -void pred_transformer::remove_predecessors(expr_ref_vector& literals) -{ - // remove tags - for (unsigned i = 0; i < literals.size(); ) { - expr* l = literals[i].get(); - m.is_not(l, l); - if (m_tag2rule.contains(l)) { - literals[i] = literals.back(); - literals.pop_back(); - } else { - ++i; - } - } -} - -void pred_transformer::simplify_formulas() -{ - m_frames.simplify_formulas (); -} - - -expr_ref pred_transformer::get_formulas(unsigned level, bool add_axioms) -{ - expr_ref_vector res(m); - if (add_axioms) { - res.push_back(pm.get_background()); - res.push_back((level == 0)?initial_state():transition()); - } - m_frames.get_frame_geq_lemmas (level, res); - return pm.mk_and(res); -} - -bool pred_transformer::propagate_to_next_level (unsigned src_level) -{return m_frames.propagate_to_next_level (src_level);} - - -/// \brief adds a lema to the solver and to child solvers -void pred_transformer::add_lemma_core(lemma* lemma) -{ - unsigned lvl = lemma->level(); - expr* l = lemma->get_expr(); - SASSERT(!lemma->is_ground() || is_clause(m, l)); - SASSERT(!is_quantifier(l) || is_clause(m, to_quantifier(l)->get_expr())); - - TRACE("spacer", tout << "add-lemma-core: " << pp_level (lvl) - << " " << head ()->get_name () - << " " << mk_pp (l, m) << "\n";); - - TRACE("core_array_eq", tout << "add-lemma-core: " << pp_level (lvl) - << " " << head ()->get_name () - << " " << mk_pp (l, m) << "\n";); - - STRACE ("spacer.expand-add", - tout << "add-lemma: " << pp_level (lvl) << " " - << head ()->get_name () << " " - << mk_epp (l, m) << "\n\n";); - - - if (is_infty_level(lvl)) { m_stats.m_num_invariants++; } - - if (lemma->is_ground()) { - if (is_infty_level(lvl)) { m_solver.assert_expr(l); } - else { - ensure_level (lvl); - m_solver.assert_expr (l, lvl); - } - } - - for (unsigned i = 0, sz = m_use.size (); i < sz; ++i) - { m_use [i]->add_lemma_from_child(*this, lemma, next_level(lvl)); } -} - -bool pred_transformer::add_lemma (expr *e, unsigned lvl) { - lemma_ref lem = alloc(lemma, m, e, lvl); - return m_frames.add_lemma(lem.get()); -} - -void pred_transformer::add_lemma_from_child (pred_transformer& child, - lemma* lemma, unsigned lvl) -{ - ensure_level(lvl); - expr_ref_vector fmls(m); - mk_assumptions(child.head(), lemma->get_expr(), fmls); - - for (unsigned i = 0; i < fmls.size(); ++i) { - expr_ref_vector inst(m); - expr* a = to_app(fmls.get(i))->get_arg(0); - expr* l = to_app(fmls.get(i))->get_arg(1); - if (get_context().use_instantiate()) - { lemma->mk_insts(inst, l); } - for (unsigned j=0; j < inst.size(); j++) { - inst.set(j, m.mk_implies(a, inst.get(j))); - } - if (lemma->is_ground() || get_context().use_qlemmas()) - { inst.push_back(fmls.get(i)); } - SASSERT (!inst.empty ()); - for (unsigned j = 0; j < inst.size(); ++j) { - TRACE("spacer_detail", tout << "child property: " - << mk_pp(inst.get (j), m) << "\n";); - if (is_infty_level(lvl)) - { m_solver.assert_expr(inst.get(j)); } - else - { m_solver.assert_expr(inst.get(j), lvl); } - } - } - -} - -expr* pred_transformer::mk_fresh_reach_case_var () -{ - std::stringstream name; - func_decl_ref decl(m); - - name << head ()->get_name () << "#reach_case_" << m_reach_case_vars.size (); - decl = m.mk_func_decl (symbol (name.str ().c_str ()), 0, - (sort*const*)nullptr, m.mk_bool_sort ()); - m_reach_case_vars.push_back (m.mk_const (pm.get_n_pred (decl))); - return m_reach_case_vars.back (); -} - -expr* pred_transformer::get_reach_case_var (unsigned idx) const -{return m_reach_case_vars.get (idx);} - - -void pred_transformer::add_reach_fact (reach_fact *fact) -{ - timeit _timer (is_trace_enabled("spacer_timeit"), - "spacer::pred_transformer::add_reach_fact", - verbose_stream ()); - - TRACE ("spacer", - tout << "add_reach_fact: " << head()->get_name() << " " - << (fact->is_init () ? "INIT " : "") - << mk_pp(fact->get (), m) << "\n";); - - // -- avoid duplicates - if (fact == nullptr || get_reach_fact(fact->get())) { return; } - - // all initial facts are grouped together - SASSERT (!fact->is_init () || m_reach_facts.empty () || - m_reach_facts.back ()->is_init ()); - - m_reach_facts.push_back (fact); - if (fact->is_init()) { m_rf_init_sz++; } - - - // update m_reach_ctx - expr_ref last_var (m); - expr_ref new_var (m); - expr_ref fml (m); - - if (!m_reach_case_vars.empty()) { last_var = m_reach_case_vars.back(); } - if (fact->is_init () || !ctx.get_params ().spacer_reach_as_init ()) - { new_var = mk_fresh_reach_case_var(); } - else { - new_var = extend_initial (fact->get ())->get_arg (0); - m_reach_case_vars.push_back (new_var); - } - - SASSERT (m_reach_facts.size () == m_reach_case_vars.size ()); - - if (last_var) - { fml = m.mk_or(m.mk_not(last_var), fact->get(), new_var); } - else - { fml = m.mk_or(fact->get(), new_var); } - - m_reach_ctx->assert_expr (fml); - TRACE ("spacer", - tout << "updating reach ctx: " << mk_pp(fml, m) << "\n";); - - lemma lem(m, fml, infty_level()); - // update users; reach facts are independent of levels - for (unsigned i = 0; i < m_use.size(); ++i) { - m_use[i]->add_lemma_from_child (*this, &lem, infty_level ()); - } -} - -expr_ref pred_transformer::get_reachable() -{ - expr_ref res(m); - res = m.mk_false(); - - if (!m_reach_facts.empty()) { - expr_substitution sub(m); - expr_ref c(m), v(m); - for (unsigned i = 0, sz = sig_size(); i < sz; ++i) { - c = m.mk_const(pm.o2n(sig(i), 0)); - v = m.mk_var(i, sig(i)->get_range()); - sub.insert(c, v); - } - scoped_ptr rep = mk_expr_simp_replacer(m); - rep->set_substitution(&sub); - - expr_ref_vector args(m); - for (unsigned i = 0, sz = m_reach_facts.size (); i < sz; ++i) { - reach_fact *f; - f = m_reach_facts.get(i); - expr_ref r(m); - r = f->get(); - const ptr_vector &aux = f->aux_vars(); - if (!aux.empty()) { - // -- existentially quantify auxiliary variables - r = mk_exists (m, aux.size(), aux.c_ptr(), r); - // XXX not sure how this interacts with variable renaming later on. - // XXX For now, simply dissallow existentially quantified auxiliaries - NOT_IMPLEMENTED_YET(); - } - (*rep)(r); - - args.push_back (r); - } - res = mk_or(args); - } - return res; -} - -expr* pred_transformer::get_last_reach_case_var () const -{ - return m_reach_case_vars.empty () ? nullptr : m_reach_case_vars.back (); -} - -expr_ref pred_transformer::get_cover_delta(func_decl* p_orig, int level) -{ - expr_ref result(m.mk_true(), m), v(m), c(m); - - expr_ref_vector lemmas (m); - m_frames.get_frame_lemmas (level == -1 ? infty_level() : level, lemmas); - if (!lemmas.empty()) { result = pm.mk_and(lemmas); } - - // replace local constants by bound variables. - expr_substitution sub(m); - for (unsigned i = 0; i < sig_size(); ++i) { - c = m.mk_const(pm.o2n(sig(i), 0)); - v = m.mk_var(i, sig(i)->get_range()); - sub.insert(c, v); - } - scoped_ptr rep = mk_default_expr_replacer(m); - rep->set_substitution(&sub); - (*rep)(result); - - // adjust result according to model converter. - unsigned arity = m_head->get_arity(); - model_ref md = alloc(model, m); - if (arity == 0) { - md->register_decl(m_head, result); - } else { - func_interp* fi = alloc(func_interp, m, arity); - fi->set_else(result); - md->register_decl(m_head, fi); - } - model_converter_ref mc = ctx.get_model_converter(); - apply(mc, md); - if (p_orig->get_arity() == 0) { - result = md->get_const_interp(p_orig); - } else { - result = md->get_func_interp(p_orig)->get_interp(); - } - return result; -} - -/** - * get an origin summary used by this transformer in the given model - * level is the level at which may summaries are obtained - * oidx is the origin index of this predicate in the model - * must indicates whether a must or a may summary is requested - * - * returns an implicant of the summary - */ -expr_ref pred_transformer::get_origin_summary (model_evaluator_util &mev, - unsigned level, - unsigned oidx, - bool must, - const ptr_vector **aux) -{ - expr_ref_vector summary (m); - expr_ref v(m); - - if (!must) { // use may summary - summary.push_back (get_formulas (level, false)); - // -- no auxiliary variables in lemmas - *aux = nullptr; - } else { // find must summary to use - reach_fact *f = get_used_origin_reach_fact (mev, oidx); - summary.push_back (f->get ()); - *aux = &f->aux_vars (); - } - - SASSERT (!summary.empty ()); - - // -- convert to origin - for (unsigned i = 0; i < summary.size(); ++i) { - pm.formula_n2o (summary.get (i), v, oidx); - summary[i] = v; - } - - // -- pick an implicant - expr_ref_vector literals (m); - compute_implicant_literals (mev, summary, literals); - - return get_manager ().mk_and (literals); -} - - -void pred_transformer::add_cover(unsigned level, expr* property) -{ - // replace bound variables by local constants. - expr_ref result(property, m), v(m), c(m); - expr_substitution sub(m); - for (unsigned i = 0; i < sig_size(); ++i) { - c = m.mk_const(pm.o2n(sig(i), 0)); - v = m.mk_var(i, sig(i)->get_range()); - sub.insert(v, c); - } - scoped_ptr rep = mk_default_expr_replacer(m); - rep->set_substitution(&sub); - (*rep)(result); - TRACE("spacer", tout << "cover:\n" << mk_pp(result, m) << "\n";); - - // add the property. - expr_ref_vector lemmas(m); - flatten_and(result, lemmas); - for (unsigned i = 0, sz = lemmas.size(); i < sz; ++i) { - add_lemma(lemmas.get(i), level); - } -} - -void pred_transformer::propagate_to_infinity (unsigned level) -{m_frames.propagate_to_infinity (level);} - - - -/// \brief Returns true if the obligation is already blocked by current lemmas -bool pred_transformer::is_blocked (pob &n, unsigned &uses_level) -{ - ensure_level (n.level ()); - prop_solver::scoped_level _sl (m_solver, n.level ()); - m_solver.set_core (nullptr); - m_solver.set_model (nullptr); - - expr_ref_vector post(m), aux(m); - post.push_back (n.post ()); - lbool res = m_solver.check_assumptions (post, aux, 0, nullptr, 0); - if (res == l_false) { uses_level = m_solver.uses_level(); } - return res == l_false; -} - -bool pred_transformer::is_qblocked (pob &n) -{ - // XXX Trivial implementation to get us started - smt::kernel solver (m, get_manager ().fparams2()); - expr_ref_vector frame_lemmas(m); - m_frames.get_frame_geq_lemmas (n.level (), frame_lemmas); - - // assert all lemmas - for (unsigned i = 0, sz = frame_lemmas.size (); i < sz; ++i) - { solver.assert_expr(frame_lemmas.get(i)); } - // assert cti - solver.assert_expr (n.post ()); - lbool res = solver.check (); - - return res == l_false; -} - -// -// check if predicate transformer has a satisfiable predecessor state. -// returns either a satisfiable predecessor state or -// return a property that blocks state and is implied by the -// predicate transformer (or some unfolding of it). -// -lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, - model_ref* model, unsigned& uses_level, - bool& is_concrete, datalog::rule const*& r, - vector& reach_pred_used, - unsigned& num_reuse_reach) -{ - TRACE("spacer", - tout << "is-reachable: " << head()->get_name() << " level: " - << n.level() << " depth: " << n.depth () << "\n"; - tout << mk_pp(n.post(), m) << "\n";); - timeit _timer (is_trace_enabled("spacer_timeit"), - "spacer::pred_transformer::is_reachable", - verbose_stream ()); - - ensure_level(n.level()); - - // prepare the solver - prop_solver::scoped_level _sl(m_solver, n.level()); - prop_solver::scoped_subset_core _sc (m_solver, !n.use_farkas_generalizer ()); - m_solver.set_core(core); - m_solver.set_model(model); - - expr_ref_vector post (m), reach_assumps (m); - post.push_back (n.post ()); - - // populate reach_assumps - - // XXX eager_reach_check must always be - // XXX enabled. Otherwise, we can get into an infinite loop in - // XXX which a model is consistent with a must-summary, but the - // XXX appropriate assumption is not set correctly by the model. - // XXX Original code handled reachability-events differently. - if (/* ctx.get_params ().eager_reach_check () && */ - n.level () > 0 && !m_all_init) { - obj_map::iterator it = m_tag2rule.begin (), - end = m_tag2rule.end (); - for (; it != end; ++it) { - datalog::rule const* r = it->m_value; - if (!r) { continue; } - find_predecessors(*r, m_predicates); - if (m_predicates.empty()) { continue; } - for (unsigned i = 0; i < m_predicates.size(); i++) { - const pred_transformer &pt = - ctx.get_pred_transformer (m_predicates [i]); - if (pt.has_reach_facts()) { - expr_ref a(m); - pm.formula_n2o (pt.get_last_reach_case_var (), a, i); - reach_assumps.push_back (m.mk_not (a)); - } else if (ctx.get_params().spacer_init_reach_facts()) { - reach_assumps.push_back (m.mk_not (it->m_key)); - break; - } - } - } - } - - TRACE ("spacer", - if (!reach_assumps.empty ()) { - tout << "reach assumptions\n"; - for (unsigned i = 0; i < reach_assumps.size (); i++) { - tout << mk_pp (reach_assumps.get (i), m) << "\n"; - } - } - ); - - // check local reachability; - // result is either sat (with some reach assumps) or - // unsat (even with no reach assumps) - expr *bg = m_extend_lit.get (); - lbool is_sat = m_solver.check_assumptions (post, reach_assumps, 1, &bg, 0); - - TRACE ("spacer", - if (!reach_assumps.empty ()) { - tout << "reach assumptions used\n"; - for (unsigned i = 0; i < reach_assumps.size (); i++) { - tout << mk_pp (reach_assumps.get (i), m) << "\n"; - } - } - ); - - if (is_sat == l_true || is_sat == l_undef) { - if (core) { core->reset(); } - if (model) { - r = find_rule (**model, is_concrete, reach_pred_used, num_reuse_reach); - TRACE ("spacer", tout << "reachable " - << "is_concrete " << is_concrete << " rused: "; - for (unsigned i = 0, sz = reach_pred_used.size (); i < sz; ++i) - tout << reach_pred_used [i]; - tout << "\n";); - } - - return is_sat; - } - if (is_sat == l_false) { - SASSERT (reach_assumps.empty ()); - TRACE ("spacer", tout << "unreachable with lemmas\n"; - if (core) { - tout << "Core:\n"; - for (unsigned i = 0; i < core->size (); i++) { - tout << mk_pp (core->get(i), m) << "\n"; - } - } - ); - uses_level = m_solver.uses_level(); - return l_false; - } - UNREACHABLE(); - return l_undef; -} - -bool pred_transformer::is_invariant(unsigned level, expr* lemma, - unsigned& solver_level, expr_ref_vector* core) -{ - expr_ref_vector conj(m), aux(m); - expr_ref glemma(m); - - if (false && is_quantifier(lemma)) { - SASSERT(is_forall(lemma)); - app_ref_vector tmp(m); - ground_expr(to_quantifier(lemma)->get_expr (), glemma, tmp); - lemma = glemma.get(); - } - - conj.push_back(mk_not(m, lemma)); - flatten_and (conj); - - prop_solver::scoped_level _sl(m_solver, level); - prop_solver::scoped_subset_core _sc (m_solver, true); - m_solver.set_core(core); - m_solver.set_model(nullptr); - expr * bg = m_extend_lit.get (); - lbool r = m_solver.check_assumptions (conj, aux, 1, &bg, 1); - if (r == l_false) { - solver_level = m_solver.uses_level (); - CTRACE ("spacer", level < m_solver.uses_level (), - tout << "Checking at level " << level - << " but only using " << m_solver.uses_level () << "\n";); - SASSERT (level <= solver_level); - } - return r == l_false; -} - -bool pred_transformer::check_inductive(unsigned level, expr_ref_vector& state, - unsigned& uses_level) -{ - manager& pm = get_manager(); - expr_ref_vector conj(m), core(m); - expr_ref states(m); - states = m.mk_not(pm.mk_and(state)); - mk_assumptions(head(), states, conj); - prop_solver::scoped_level _sl(m_solver, level); - prop_solver::scoped_subset_core _sc (m_solver, true); - m_solver.set_core(&core); - m_solver.set_model (nullptr); - expr_ref_vector aux (m); - conj.push_back (m_extend_lit); - lbool res = m_solver.check_assumptions (state, aux, conj.size (), conj.c_ptr (), 1); - if (res == l_false) { - state.reset(); - state.append(core); - uses_level = m_solver.uses_level(); - } - TRACE ("core_array_eq", - tout << "check_inductive: " - << "states: " << mk_pp (states, m) - << " is: " << res << "\n" - << "with transition: " << mk_pp (m_transition, m) << "\n";); - return res == l_false; -} - -void pred_transformer::mk_assumptions(func_decl* head, expr* fml, - expr_ref_vector& result) -{ - expr_ref tmp1(m), tmp2(m); - expr_substitution sub (m); - proof_ref pr (m.mk_asserted (m.mk_true ()), m); - obj_map::iterator it = m_tag2rule.begin(), - end = m_tag2rule.end(); - for (; it != end; ++it) { - expr* tag = it->m_key; - datalog::rule const* r = it->m_value; - if (!r) { continue; } - find_predecessors(*r, m_predicates); - for (unsigned i = 0; i < m_predicates.size(); i++) { - func_decl* d = m_predicates[i]; - if (d == head) { - tmp1 = m.mk_implies(tag, fml); - pm.formula_n2o(tmp1, tmp2, i); - result.push_back(tmp2); - } - } - } -} - -void pred_transformer::initialize(decl2rel const& pts) -{ - m_initial_state = m.mk_false(); - m_transition = m.mk_true(); - init_rules(pts, m_initial_state, m_transition); - th_rewriter rw(m); - rw(m_transition); - rw(m_initial_state); - - m_solver.assert_expr (m_transition); - m_solver.assert_expr (m_initial_state, 0); - TRACE("spacer", - tout << "Initial state: " << mk_pp(m_initial_state, m) << "\n"; - tout << "Transition: " << mk_pp(m_transition, m) << "\n";); - SASSERT(is_app(m_initial_state)); - //m_reachable.add_init(to_app(m_initial_state)); - - -} - -void pred_transformer::init_reach_facts () -{ - expr_ref_vector v(m); - reach_fact_ref fact; - - rule2expr::iterator it = m_rule2tag.begin (), end = m_rule2tag.end (); - for (; it != end; ++it) { - const datalog::rule* r = it->m_key; - if (r->get_uninterpreted_tail_size() == 0) { - fact = alloc (reach_fact, m, *r, m_rule2transition.find (r), - get_aux_vars (*r), true); - add_reach_fact (fact.get ()); - } - } -} - -void pred_transformer::init_rules(decl2rel const& pts, expr_ref& init, expr_ref& transition) -{ - expr_ref_vector transitions(m); - ptr_vector tr_rules; - datalog::rule const* rule; - expr_ref_vector disj(m), init_conds (m); - app_ref pred(m); - vector is_init; - for (unsigned i = 0; i < rules().size(); ++i) { - init_rule(pts, *rules()[i], is_init, tr_rules, transitions); - } - SASSERT (is_init.size () == transitions.size ()); - switch(transitions.size()) { - case 0: - transition = m.mk_false(); - break; - case 1: { - std::stringstream name; - // create a dummy tag. - name << head()->get_name() << "_dummy"; - pred = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); - rule = tr_rules[0]; - m_tag2rule.insert(pred, rule); - m_rule2tag.insert(rule, pred.get()); - transitions [0] = m.mk_implies (pred, transitions.get (0)); - transitions.push_back (m.mk_or (pred, m_extend_lit->get_arg (0))); - if (!is_init [0]) { init_conds.push_back(m.mk_not(pred)); } - - transition = pm.mk_and(transitions); - break; - } - default: - disj.push_back (m_extend_lit->get_arg (0)); - for (unsigned i = 0; i < transitions.size(); ++i) { - std::stringstream name; - name << head()->get_name() << "_tr" << i; - pred = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); - rule = tr_rules[i]; - m_tag2rule.insert(pred, rule); - m_rule2tag.insert(rule, pred); - disj.push_back(pred); - transitions[i] = m.mk_implies(pred, transitions[i].get()); - // update init conds - if (!is_init[i]) { - init_conds.push_back (m.mk_not (pred)); - } - } - transitions.push_back(m.mk_or(disj.size(), disj.c_ptr())); - transition = pm.mk_and(transitions); - break; - } - // mk init condition - init = pm.mk_and (init_conds); - if (init_conds.empty ()) { // no rule has uninterpreted tail - m_all_init = true; - } -} - -void pred_transformer::init_rule( - decl2rel const& pts, - datalog::rule const& rule, - vector& is_init, - ptr_vector& rules, - expr_ref_vector& transitions) -{ - scoped_watch _t_(m_initialize_watch); - - // Predicates that are variable representatives. Other predicates at - // positions the variables occur are made equivalent with these. - expr_ref_vector conj(m); - app_ref_vector& var_reprs = *(alloc(app_ref_vector, m)); - ptr_vector aux_vars; - - unsigned ut_size = rule.get_uninterpreted_tail_size(); - unsigned t_size = rule.get_tail_size(); - SASSERT(ut_size <= t_size); - init_atom(pts, rule.get_head(), var_reprs, conj, UINT_MAX); - for (unsigned i = 0; i < ut_size; ++i) { - if (rule.is_neg_tail(i)) { - throw default_exception("SPACER does not support negated predicates in rule tails"); - } - init_atom(pts, rule.get_tail(i), var_reprs, conj, i); - } - // -- substitute free variables - expr_ref fml(m); { - expr_ref_vector tail(m); - for (unsigned i = ut_size; i < t_size; ++i) - { tail.push_back(rule.get_tail(i)); } - fml = mk_and (tail); - - ground_free_vars (fml, var_reprs, aux_vars, ut_size == 0); - SASSERT(check_filled(var_reprs)); - - expr_ref tmp(m); - var_subst (m, false)(fml, - var_reprs.size (), (expr*const*)var_reprs.c_ptr(), tmp); - flatten_and (tmp, conj); - fml = mk_and(conj); - conj.reset (); - } - - th_rewriter rw(m); - rw(fml); - if (ctx.get_params().spacer_blast_term_ite()) { - blast_term_ite (fml); - rw(fml); - } - TRACE("spacer", tout << mk_pp(fml, m) << "\n";); - - // allow quantifiers in init rule - SASSERT(ut_size == 0 || is_ground(fml)); - if (m.is_false(fml)) { - // no-op. - } else { - is_init.push_back (ut_size == 0); - transitions.push_back(fml); - m.inc_ref(fml); - m_rule2transition.insert(&rule, fml.get()); - rules.push_back(&rule); - } - m_rule2inst.insert(&rule,&var_reprs); - m_rule2vars.insert(&rule, aux_vars); - TRACE("spacer", - tout << rule.get_decl()->get_name() << "\n"; - for (unsigned i = 0; i < var_reprs.size(); ++i) { - tout << mk_pp(var_reprs[i].get(), m) << " "; - } - tout << "\n";); -} - -bool pred_transformer::check_filled(app_ref_vector const& v) const -{ - for (unsigned i = 0; i < v.size(); ++i) { - if (!v[i]) { return false; } - } - return true; -} - -// create constants for free variables in tail. -void pred_transformer::ground_free_vars(expr* e, app_ref_vector& vars, - ptr_vector& aux_vars, bool is_init) -{ - expr_free_vars fv; - fv(e); - - while (vars.size() < fv.size()) { - vars.push_back(nullptr); - } - for (unsigned i = 0; i < fv.size(); ++i) { - if (fv[i] && !vars[i].get()) { - vars[i] = m.mk_fresh_const("aux", fv[i]); - vars[i] = m.mk_const (pm.get_n_pred (vars.get (i)->get_decl ())); - aux_vars.push_back(vars[i].get()); - } - } - -} - -// create names for variables used in relations. -void pred_transformer::init_atom( - decl2rel const& pts, - app * atom, - app_ref_vector& var_reprs, - expr_ref_vector& conj, - unsigned tail_idx - ) -{ - unsigned arity = atom->get_num_args(); - func_decl* head = atom->get_decl(); - pred_transformer& pt = *pts.find(head); - for (unsigned i = 0; i < arity; i++) { - app_ref rep(m); - - if (tail_idx == UINT_MAX) { - rep = m.mk_const(pm.o2n(pt.sig(i), 0)); - } else { - rep = m.mk_const(pm.o2o(pt.sig(i), 0, tail_idx)); - } - - expr * arg = atom->get_arg(i); - if (is_var(arg)) { - var * v = to_var(arg); - unsigned var_idx = v->get_idx(); - if (var_idx >= var_reprs.size()) { - var_reprs.resize(var_idx+1); + std::stable_sort (vars.c_ptr(), vars.c_ptr() + vars.size(), sk_lt_proc()); + unsigned i, j, end; + app_ref v(m); + for (i = 1, j = 1, end = vars.size(); i < end; ++i) { + if (vars.get(j-1) != vars.get(i)) { + v = vars.get(i); // keep ref + vars.set(j++, v); } - expr * repr = var_reprs[var_idx].get(); - if (repr) { - conj.push_back(m.mk_eq(rep, repr)); - } else { - var_reprs[var_idx] = rep; - } - } else { - SASSERT(is_app(arg)); - conj.push_back(m.mk_eq(rep, arg)); + } + vars.shrink(j); + } + + TRACE("spacer", tout << "Skolemizing: "; + for (auto v : vars) tout << " " << mk_pp(v, m) << " "; + tout << "\nfrom " << mk_pp(fml, m) << "\n"; + ); + + app_ref_vector pinned(m); + + expr_safe_replace sub(m); + for (unsigned i = 0, sz = vars.size(); i < sz; ++i) { + expr* e; + e = vars.get(i); + pinned.push_back (mk_zk_const (m, i, get_sort(e))); + sub.insert (e, pinned.back()); + } + sub(fml, res); +} + +pob *derivation::create_next_child(model &mdl) +{ + timeit _timer (is_trace_enabled("spacer_timeit"), + "spacer::derivation::create_next_child", + verbose_stream ()); + + ast_manager &m = get_ast_manager (); + expr_ref_vector summaries (m); + app_ref_vector vars(m); + + // -- find first may premise + while (m_active < m_premises.size() && m_premises[m_active].is_must()) { + summaries.push_back (m_premises[m_active].get_summary ()); + vars.append (m_premises[m_active].get_ovars ()); + ++m_active; + } + if (m_active >= m_premises.size()) { return nullptr; } + + // -- update m_trans with the pre-image of m_trans over the must summaries + summaries.push_back (m_trans); + m_trans = mk_and (summaries); + summaries.reset (); + + if (!vars.empty()) { + timeit _timer1 (is_trace_enabled("spacer_timeit"), + "create_next_child::qproject1", + verbose_stream ()); + vars.append(m_evars); + m_evars.reset(); + pt().mbp(vars, m_trans, mdl, + true, pt().get_context().use_ground_pob()); + m_evars.append (vars); + vars.reset(); + } + + if (!mdl.is_true(m_premises[m_active].get_summary())) { + IF_VERBOSE(1, verbose_stream() << "Summary unexpectendly not true\n";); + return nullptr; + } + + + // create the post-condition by computing a post-image over summaries + // that precede currently active premise + for (unsigned i = m_active + 1; i < m_premises.size(); ++i) { + summaries.push_back (m_premises [i].get_summary ()); + vars.append (m_premises [i].get_ovars ()); + } + summaries.push_back (m_trans); + expr_ref post(m); + post = mk_and(summaries); + summaries.reset (); + + if (!vars.empty()) { + timeit _timer2(is_trace_enabled("spacer_timeit"), + "create_next_child::qproject2", + verbose_stream ()); + // include m_evars in case they can eliminated now as well + vars.append(m_evars); + pt().mbp(vars, post, mdl, + true, pt().get_context().use_ground_pob()); + //qe::reduce_array_selects (*mev.get_model (), post); + } + else { + // if no variables to eliminate, don't forget about m_evars + // that occur in m_trans + vars.append(m_evars); + } + + if (!vars.empty()) { + // existentially quantify out vars from post and skolemize the result + exist_skolemize(post.get(), vars, post); + } + + get_manager ().formula_o2n (post.get (), post, + m_premises [m_active].get_oidx (), + vars.empty()); + + + /* The level and depth are taken from the parent, not the sibling. + The reasoning is that the sibling has not been checked before, + and lower level is a better starting point. */ + pob *n = m_premises[m_active].pt().mk_pob(&m_parent, + prev_level (m_parent.level ()), + m_parent.depth (), post, vars); + IF_VERBOSE (1, verbose_stream () + << "\n\tcreate_child: " << n->pt ().head ()->get_name () + << " (" << n->level () << ", " << n->depth () << ") " + << (n->use_farkas_generalizer () ? "FAR " : "SUB ") + << n->post ()->get_id (); + verbose_stream().flush ();); + return n; +} + +pob *derivation::create_next_child () +{ + if (m_active + 1 >= m_premises.size()) { return nullptr; } + + // update the summary of the active node to some must summary + + // construct a new model consistent with the must summary of m_active premise + pred_transformer &pt = m_premises[m_active].pt (); + + ast_manager &m = get_ast_manager (); + manager &pm = get_manager (); + + expr_ref_vector summaries (m); + + for (unsigned i = m_active + 1; i < m_premises.size (); ++i) + { summaries.push_back(m_premises [i].get_summary()); } + + // -- orient transition relation towards m_active premise + expr_ref active_trans (m); + pm.formula_o2n (m_trans, active_trans, + m_premises[m_active].get_oidx (), false); + summaries.push_back (active_trans); + + // if not true, bail out, the must summary of m_active is not strong enough + // this is possible if m_post was weakened for some reason + model_ref mdl; + if (!pt.is_must_reachable(mk_and(summaries), &mdl)) { return nullptr; } + mdl->set_model_completion(false); + + // find must summary used + reach_fact *rf = pt.get_used_rf (*mdl, true); + + // get an implicant of the summary + expr_ref_vector u(m), lits(m); + u.push_back (rf->get ()); + compute_implicant_literals (*mdl, u, lits); + expr_ref v(m); + v = mk_and (lits); + + // XXX The summary is not used by anyone after this point + m_premises[m_active].set_summary (v, true, &(rf->aux_vars ())); + + + /** HACK: needs a rewrite + * compute post over the new must summary this must be done here + * because the must summary is currently described over new + * variables. However, we store it over old-variables, but we do + * not update the model. So we must get rid of all of the + * new-variables at this point. + */ + { + pred_transformer &pt = m_premises[m_active].pt (); + app_ref_vector vars (m); + + summaries.reset (); + summaries.push_back (v); + summaries.push_back (active_trans); + m_trans = mk_and (summaries); + + // variables to eliminate + vars.append (rf->aux_vars ().size (), rf->aux_vars ().c_ptr ()); + for (unsigned i = 0, sz = pt.head ()->get_arity (); i < sz; ++i) + { vars.push_back(m.mk_const(pm.o2n(pt.sig(i), 0))); } + + if (!vars.empty ()) { + vars.append(m_evars); + m_evars.reset(); + this->pt().mbp(vars, m_trans, *mdl, + true, this->pt().get_context().use_ground_pob()); + // keep track of implicitly quantified variables + m_evars.append (vars); + vars.reset(); } } + + m_active++; + + return create_next_child (*mdl); } -void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r) +/// derivation::premise + +derivation::premise::premise (pred_transformer &pt, unsigned oidx, + expr *summary, bool must, + const ptr_vector *aux_vars) : + m_pt (pt), m_oidx (oidx), + m_summary (summary, pt.get_ast_manager ()), m_must (must), + m_ovars (pt.get_ast_manager ()) { - r.push_back(pm.get_background()); - r.push_back((lvl == 0)?initial_state():transition()); - for (unsigned i = 0; i < rules().size(); ++i) { - add_premises(pts, lvl, *rules()[i], r); - } + + ast_manager &m = m_pt.get_ast_manager (); + manager &sm = m_pt.get_manager (); + + unsigned sig_sz = m_pt.head ()->get_arity (); + for (unsigned i = 0; i < sig_sz; ++i) + { m_ovars.push_back(m.mk_const(sm.o2o(pt.sig(i), 0, m_oidx))); } + + if (aux_vars) + for (unsigned i = 0, sz = aux_vars->size (); i < sz; ++i) + { m_ovars.push_back(m.mk_const(sm.n2o(aux_vars->get(i)->get_decl(), m_oidx))); } } -void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r) +derivation::premise::premise (const derivation::premise &p) : + m_pt (p.m_pt), m_oidx (p.m_oidx), m_summary (p.m_summary), m_must (p.m_must), + m_ovars (p.m_ovars) {} + +/// \brief Updated the summary. +/// The new summary is over n-variables. +void derivation::premise::set_summary (expr * summary, bool must, + const ptr_vector *aux_vars) { - find_predecessors(rule, m_predicates); - for (unsigned i = 0; i < m_predicates.size(); ++i) { - expr_ref tmp(m); - func_decl* head = m_predicates[i]; - pred_transformer& pt = *pts.find(head); - expr_ref inv = pt.get_formulas(lvl, false); - if (!m.is_true(inv)) { - pm.formula_n2o(inv, tmp, i, true); - r.push_back(tmp); - } - } + ast_manager &m = m_pt.get_ast_manager (); + manager &sm = m_pt.get_manager (); + unsigned sig_sz = m_pt.head ()->get_arity (); + + m_must = must; + sm.formula_n2o (summary, m_summary, m_oidx); + + m_ovars.reset (); + for (unsigned i = 0; i < sig_sz; ++i) + { m_ovars.push_back(m.mk_const(sm.o2o(m_pt.sig(i), 0, m_oidx))); } + + if (aux_vars) + for (unsigned i = 0, sz = aux_vars->size (); i < sz; ++i) + m_ovars.push_back (m.mk_const (sm.n2o (aux_vars->get (i)->get_decl (), + m_oidx))); } -void pred_transformer::inherit_properties(pred_transformer& other) -{ - m_frames.inherit_frames (other.m_frames); -} +/// Lemma lemma::lemma (ast_manager &manager, expr * body, unsigned lvl) : m_ref_count(0), m(manager), m_body(body, m), m_cube(m), - m_bindings(m), m_lvl(lvl), - m_pob(nullptr), m_new_pob(false) { + m_zks(m), m_bindings(m), m_lvl(lvl), m_init_lvl(m_lvl), + m_pob(nullptr), m_ctp(nullptr), m_external(false), m_bumped(0) { SASSERT(m_body); normalize(m_body, m_body); } @@ -1118,33 +471,51 @@ lemma::lemma (ast_manager &manager, expr * body, unsigned lvl) : lemma::lemma(pob_ref const &p) : m_ref_count(0), m(p->get_ast_manager()), m_body(m), m_cube(m), - m_bindings(m), m_lvl(p->level()), - m_pob(p), m_new_pob(m_pob) {SASSERT(m_pob);} + m_zks(m), m_bindings(m), m_lvl(p->level()), m_init_lvl(m_lvl), + m_pob(p), m_ctp(nullptr), m_external(false), m_bumped(0) { + SASSERT(m_pob); + m_pob->get_skolems(m_zks); + add_binding(m_pob->get_binding()); +} lemma::lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl) : m_ref_count(0), m(p->get_ast_manager()), m_body(m), m_cube(m), - m_bindings(m), m_lvl(p->level()), - m_pob(p), m_new_pob(m_pob) + m_zks(m), m_bindings(m), m_lvl(p->level()), m_init_lvl(m_lvl), + m_pob(p), m_ctp(nullptr), m_external(false), m_bumped(0) { + if (m_pob) { + m_pob->get_skolems(m_zks); + add_binding(m_pob->get_binding()); + } update_cube(p, cube); set_level(lvl); } +void lemma::add_skolem(app *zk, app *b) { + SASSERT(m_bindings.size() == m_zks.size()); + // extend bindings + m_bindings.push_back(b); + // extend skolems + m_zks.push_back(zk); +} + + void lemma::mk_expr_core() { - if (m_body) return; + if (m_body) {return;} - if (m_pob) { - mk_cube_core(); + if (m_pob) {mk_cube_core();} - // make a clause by negating the cube - m_body = ::push_not(::mk_and(m_cube)); - normalize(m_body, m_body); + SASSERT(!m_cube.empty()); + m_body = ::mk_and(m_cube); + // normalize works better with a cube + normalize(m_body, m_body); + m_body = ::push_not(m_body); - if (!m_pob->is_ground() && has_zk_const(m_body)) { + if (!m_zks.empty() && has_zk_const(m_body)) { app_ref_vector zks(m); - m_pob->get_skolems(zks); + zks.append(m_zks); zks.reverse(); expr_abstract(m, 0, zks.size(), (expr* const*)zks.c_ptr(), m_body, @@ -1158,21 +529,7 @@ void lemma::mk_expr_core() { m_body = m.mk_quantifier(true, zks.size(), sorts.c_ptr(), names.c_ptr(), - m_body, 0, symbol(m_body->get_id())); - if (m_new_pob) { - add_binding(m_pob->get_binding()); - } - } - m_new_pob = false; - return; - } - else if (!m_cube.empty()) { - m_body = ::push_not(::mk_and(m_cube)); - normalize(m_body, m_body); - return; - } - else { - UNREACHABLE(); + m_body, 15, symbol(m_body->get_id())); } SASSERT(m_body); } @@ -1180,9 +537,7 @@ void lemma::mk_cube_core() { if (!m_cube.empty()) {return;} expr_ref cube(m); if (m_pob || m_body) { - if(m_pob) { - cube = m_pob->post(); - } + if (m_pob) {cube = m_pob->post();} else if (m_body) { // no quantifiers for now SASSERT(!is_quantifier(m_body)); @@ -1228,66 +583,1385 @@ void lemma::update_cube (pob_ref const &p, expr_ref_vector &cube) { m_body.reset(); m_cube.append(cube); if (m_cube.empty()) {m_cube.push_back(m.mk_true());} + + // after the cube is updated, if there are no skolems, + // convert the lemma to quantifier-free + bool is_quant = false; + for (unsigned i = 0, sz = cube.size(); !is_quant && i < sz; ++i) { + is_quant = has_zk_const(cube.get(i)); + } + + if (!is_quant) { + m_zks.reset(); + m_bindings.reset(); + } } +bool lemma::has_binding(app_ref_vector const &binding) { + unsigned num_decls = m_zks.size(); + + SASSERT(binding.size() == num_decls); + + if (num_decls == 0) return true; + + for (unsigned off = 0, sz = m_bindings.size(); off < sz; off += num_decls) { + unsigned i = 0; + for (; i < num_decls; ++i) { + if (m_bindings.get(off + i) != binding.get(i)) { + break; + } + } + if (i == num_decls) return true; + } + return false; +} +void lemma::add_binding(app_ref_vector const &binding) { + if (!has_binding(binding)) { + m_bindings.append(binding); + + TRACE("spacer", + tout << "new binding: "; + for (unsigned i = 0; i < binding.size(); i++) + tout << mk_pp(binding.get(i), m) << " "; + tout << "\n";); + } +} +void lemma::instantiate(expr * const * exprs, expr_ref &result, expr *e) { + expr *lem = e == nullptr ? get_expr() : e; + if (!is_quantifier (lem) || m_bindings.empty()) {return;} + + expr *body = to_quantifier(lem)->get_expr(); + unsigned num_decls = to_quantifier(lem)->get_num_decls(); + var_subst vs(m, false); + vs(body, num_decls, exprs, result); +} + +void lemma::set_level (unsigned lvl) { + if (m_pob){m_pob->blocked_at(lvl);} + m_lvl = lvl; +} + + void lemma::mk_insts(expr_ref_vector &out, expr* e) { expr *lem = e == nullptr ? get_expr() : e; if (!is_quantifier (lem) || m_bindings.empty()) {return;} - expr *body = to_quantifier(lem)->get_expr(); unsigned num_decls = to_quantifier(lem)->get_num_decls(); expr_ref inst(m); - var_subst vs(m, false); - for (unsigned i = 0, - sz = m_bindings.size() / num_decls, - off = 0; - i < sz; - ++i, off += num_decls) { - inst.reset(); - vs.reset(); - vs(body, num_decls, (expr**) m_bindings.c_ptr() + off, inst); + for (unsigned off = 0, sz = m_bindings.size(); off < sz; off += num_decls) { + instantiate((expr * const *) m_bindings.c_ptr() + off, inst, e); out.push_back(inst); + inst.reset(); } } -bool pred_transformer::frames::add_lemma(lemma *lem) -{ - TRACE("spacer", tout << "add-lemma: " << pp_level(lem->level()) << " " - << m_pt.head()->get_name() << " " - << mk_pp(lem->get_expr(), m_pt.get_ast_manager()) << "\n";); +// ---------------- +// pred_tansformer +pred_transformer::pt_rule &pred_transformer::pt_rules::mk_rule(const pred_transformer::pt_rule &v) { + pt_rule *p = nullptr; + if (find_by_rule(v.rule(), p)) + return *p; - for (unsigned i = 0, sz = m_lemmas.size(); i < sz; ++i) { - if (m_lemmas [i]->get_expr() == lem->get_expr()) { - // extend bindings if needed - if (!lem->get_bindings().empty()) { - m_lemmas [i]->add_binding(lem->get_bindings()); + p = alloc(pt_rule, v); + m_rules.insert(&p->rule(), p); + if (p->tag()) m_tags.insert(p->tag(), p); + return *p; +} + +pred_transformer::pred_transformer(context& ctx, manager& pm, func_decl* head): + pm(pm), m(pm.get_manager()), + ctx(ctx), m_head(head, m), + m_sig(m), + m_reach_solver (ctx.mk_solver2()), + m_pobs(*this), + m_frames(*this), + m_reach_facts(), m_rf_init_sz(0), + m_transition_clause(m), m_transition(m), m_init(m), + m_extend_lit0(m), m_extend_lit(m), + m_all_init(false) +{ + m_solver = alloc(prop_solver, m, ctx.mk_solver0(), ctx.mk_solver1(), + ctx.get_params(), head->get_name()); + init_sig (); + + m_extend_lit = mk_extend_lit(); + m_extend_lit0 = m_extend_lit; +} + +app_ref pred_transformer::mk_extend_lit() { + app_ref v(m); + std::stringstream name; + name << m_head->get_name () << "_ext0"; + v = m.mk_const (symbol(name.str().c_str()), m.mk_bool_sort()); + return app_ref(m.mk_not (m.mk_const (pm.get_n_pred (v->get_decl ()))), m); +} + + +std::ostream& pred_transformer::display(std::ostream& out) const +{ + if (!rules().empty()) { out << "rules\n"; } + datalog::rule_manager& rm = ctx.get_datalog_context().get_rule_manager(); + for (unsigned i = 0; i < rules().size(); ++i) { + rm.display_smt2(*rules()[i], out) << "\n"; + } + out << "transition\n" << mk_pp(transition(), m) << "\n"; + return out; +} + +void pred_transformer::collect_statistics(statistics& st) const +{ + m_solver->collect_statistics(st); + + // -- number of times a lemma has been propagated to a higher level + // -- during push + st.update("SPACER num propagations", m_stats.m_num_propagations); + // -- number of lemmas in all current frames + st.update("SPACER num active lemmas", m_frames.lemma_size ()); + // -- number of lemmas that are inductive invariants + st.update("SPACER num invariants", m_stats.m_num_invariants); + // -- number of proof obligations (0 if pobs are not reused) + st.update("SPACER num pobs", m_pobs.size()); + + // -- number of reach facts created + st.update("SPACER num reach queries", m_stats.m_num_reach_queries); + + st.update("SPACER num ctp blocked", m_stats.m_num_ctp_blocked); + st.update("SPACER num is_invariant", m_stats.m_num_is_invariant); + st.update("SPACER num lemma jumped", m_stats.m_num_lemma_level_jump); + + // -- time in rule initialization + st.update ("time.spacer.init_rules.pt.init", m_initialize_watch.get_seconds ()); + // -- time is must_reachable() + st.update ("time.spacer.solve.pt.must_reachable", + m_must_reachable_watch.get_seconds ()); + st.update("time.spacer.ctp", m_ctp_watch.get_seconds()); + st.update("time.spacer.mbp", m_mbp_watch.get_seconds()); +} + +void pred_transformer::reset_statistics() +{ + m_solver->reset_statistics(); + //m_reachable.reset_statistics(); + m_stats.reset(); + m_initialize_watch.reset (); + m_must_reachable_watch.reset (); + m_ctp_watch.reset(); + m_mbp_watch.reset(); +} + +void pred_transformer::init_sig() +{ + for (unsigned i = 0; i < m_head->get_arity(); ++i) { + sort * arg_sort = m_head->get_domain(i); + std::stringstream name_stm; + name_stm << m_head->get_name() << '_' << i; + func_decl_ref stm(m); + stm = m.mk_func_decl(symbol(name_stm.str().c_str()), 0, (sort*const*)nullptr, arg_sort); + m_sig.push_back(pm.get_o_pred(stm, 0)); + } +} + +void pred_transformer::ensure_level(unsigned level) +{ + if (is_infty_level(level)) { return; } + + while (m_frames.size() <= level) { + m_frames.add_frame (); + m_solver->add_level (); + } +} + +bool pred_transformer::is_must_reachable(expr* state, model_ref* model) +{ + scoped_watch _t_(m_must_reachable_watch); + SASSERT (state); + // XXX This seems to mis-handle the case when state is + // reachable using the init rule of the current transformer + if (m_reach_facts.empty()) { return false; } + + m_reach_solver->push (); + m_reach_solver->assert_expr (state); + m_reach_solver->assert_expr (m.mk_not (m_reach_facts.back()->tag())); + lbool res = m_reach_solver->check_sat (0, nullptr); + if (model) { m_reach_solver->get_model(*model); } + m_reach_solver->pop (1); + return (res == l_true); +} + + + + +reach_fact* pred_transformer::get_used_rf (model& mdl, bool all) { + expr_ref v (m); + model::scoped_model_completion _sc_(mdl, false); + + for (auto *rf : m_reach_facts) { + if (!all && rf->is_init()) continue; + if (mdl.is_false(rf->tag())) return rf; + } + UNREACHABLE(); + return nullptr; +} + +reach_fact *pred_transformer::get_used_origin_rf(model& mdl, unsigned oidx) { + expr_ref b(m), v(m); + model::scoped_model_completion _sc_(mdl, false); + for (auto *rf : m_reach_facts) { + pm.formula_n2o (rf->tag(), v, oidx); + if (mdl.is_false(v)) return rf; + } + UNREACHABLE(); + return nullptr; +} + +const datalog::rule *pred_transformer::find_rule(model &model) { + expr_ref val(m); + + for (auto &kv : m_pt_rules) { + app *tag = kv.m_value->tag(); + if (model.eval(tag->get_decl(), val) && m.is_true(val)) { + return &kv.m_value->rule(); + } + } + return nullptr; +} + +const datalog::rule *pred_transformer::find_rule(model &model, + bool& is_concrete, + vector& reach_pred_used, + unsigned& num_reuse_reach) +{ + // find a rule whose tag is true in the model; + // prefer a rule where the model intersects with reach facts of all predecessors; + // also find how many predecessors' reach facts are true in the model + expr_ref vl(m); + const datalog::rule *r = ((datalog::rule*)nullptr); + //for (auto &entry : m_tag2rule) { + for (auto &kv : m_pt_rules) { + app* tag = kv.m_value->tag(); + if (model.eval(tag->get_decl(), vl) && m.is_true(vl)) { + r = &kv.m_value->rule(); + is_concrete = true; + num_reuse_reach = 0; + reach_pred_used.reset(); + unsigned tail_sz = r->get_uninterpreted_tail_size(); + for (unsigned i = 0; i < tail_sz; i++) { + bool used = false; + func_decl* d = r->get_tail(i)->get_decl(); + const pred_transformer &pt = ctx.get_pred_transformer(d); + if (!pt.has_rfs()) {is_concrete = false;} + else { + expr_ref v(m); + pm.formula_n2o(pt.get_last_rf_tag (), v, i); + model.eval(to_app (v.get ())->get_decl (), vl); + used = m.is_false (vl); + is_concrete = is_concrete && used; + } + + reach_pred_used.push_back (used); + if (used) {num_reuse_reach++;} } - // if the lemma is at a higher level, skip it - // XXX if there are new bindings, we need to assert new instances - if (m_lemmas [i]->level() >= lem->level()) { + if (is_concrete) {break;} + } + } + // SASSERT (r); + // reached by a reachability fact + if (!r) { is_concrete = true; } + return r; +} + +void pred_transformer::find_predecessors(datalog::rule const& r, ptr_vector& preds) const +{ + preds.reset(); + unsigned tail_sz = r.get_uninterpreted_tail_size(); + for (unsigned ti = 0; ti < tail_sz; ti++) { + preds.push_back(r.get_tail(ti)->get_decl()); + } +} + +void pred_transformer::simplify_formulas() +{m_frames.simplify_formulas ();} + + +expr_ref pred_transformer::get_formulas(unsigned level) const +{ + expr_ref_vector res(m); + m_frames.get_frame_geq_lemmas (level, res); + return mk_and(res); +} + +bool pred_transformer::propagate_to_next_level (unsigned src_level) +{return m_frames.propagate_to_next_level (src_level);} + + +/// \brief adds a lemma to the solver and to child solvers +void pred_transformer::add_lemma_core(lemma* lemma, bool ground_only) +{ + unsigned lvl = lemma->level(); + expr* l = lemma->get_expr(); + SASSERT(!lemma->is_ground() || is_clause(m, l)); + SASSERT(!is_quantifier(l) || is_clause(m, to_quantifier(l)->get_expr())); + + TRACE("spacer", tout << "add-lemma-core: " << pp_level (lvl) + << " " << head ()->get_name () + << " " << mk_pp (l, m) << "\n";); + + TRACE("core_array_eq", tout << "add-lemma-core: " << pp_level (lvl) + << " " << head ()->get_name () + << " " << mk_pp (l, m) << "\n";); + + STRACE ("spacer.expand-add", + tout << "** add-lemma: " << pp_level (lvl) << " " + << head ()->get_name () << " " + << mk_epp (l, m) << "\n"; + + if (!lemma->is_ground()) { + tout << "Bindings: " << lemma->get_bindings() << "\n"; + } + tout << "\n"; + ); + + + if (is_infty_level(lvl)) { m_stats.m_num_invariants++; } + + if (lemma->is_ground()) { + if (is_infty_level(lvl)) { m_solver->assert_expr(l); } + else { + ensure_level (lvl); + m_solver->assert_expr (l, lvl); + } + } + + for (unsigned i = 0, sz = m_use.size (); i < sz; ++i) + { m_use [i]->add_lemma_from_child(*this, lemma, + next_level(lvl), ground_only); } +} + +bool pred_transformer::add_lemma (expr *e, unsigned lvl) { + lemma_ref lem = alloc(lemma, m, e, lvl); + return m_frames.add_lemma(lem.get()); +} + +void pred_transformer::add_lemma_from_child (pred_transformer& child, + lemma* lemma, unsigned lvl, + bool ground_only) +{ + ensure_level(lvl); + expr_ref_vector fmls(m); + mk_assumptions(child.head(), lemma->get_expr(), fmls); + + for (unsigned i = 0; i < fmls.size(); ++i) { + expr_ref_vector inst(m); + expr* a = to_app(fmls.get(i))->get_arg(0); + expr* l = to_app(fmls.get(i))->get_arg(1); + if (!lemma->is_ground() && get_context().use_instantiate()) { + expr_ref grnd_lemma(m); + app_ref_vector tmp(m); + lemma->mk_insts(inst, l); + // -- take ground instance of the current lemma + ground_expr(to_quantifier(l)->get_expr(), grnd_lemma, tmp); + inst.push_back(grnd_lemma); + } + for (unsigned j=0; j < inst.size(); j++) { + inst.set(j, m.mk_implies(a, inst.get(j))); + } + if (lemma->is_ground() || (get_context().use_qlemmas() && !ground_only)) { + inst.push_back(fmls.get(i)); + } + SASSERT (!inst.empty ()); + for (unsigned j = 0; j < inst.size(); ++j) { + TRACE("spacer_detail", tout << "child property: " + << mk_pp(inst.get (j), m) << "\n";); + if (is_infty_level(lvl)) { + m_solver->assert_expr(inst.get(j)); + } + else { + m_solver->assert_expr(inst.get(j), lvl); + } + } + } + +} + +app_ref pred_transformer::mk_fresh_rf_tag () +{ + std::stringstream name; + func_decl_ref decl(m); + + name << head ()->get_name () << "#reach_tag_" << m_reach_facts.size (); + decl = m.mk_func_decl (symbol (name.str ().c_str ()), 0, + (sort*const*)nullptr, m.mk_bool_sort ()); + return app_ref(m.mk_const (pm.get_n_pred (decl)), m); +} + +void pred_transformer::add_rf (reach_fact *rf) +{ + timeit _timer (is_trace_enabled("spacer_timeit"), + "spacer::pred_transformer::add_rf", + verbose_stream ()); + + TRACE ("spacer", + tout << "add_rf: " << head()->get_name() << " " + << (rf->is_init () ? "INIT " : "") + << mk_pp(rf->get (), m) << "\n";); + + // -- avoid duplicates + if (!rf || get_rf(rf->get())) {return;} + + // all initial facts are grouped together + SASSERT (!rf->is_init () || m_reach_facts.empty () || + m_reach_facts.back ()->is_init ()); + + // create tags + app_ref last_tag(m); + app_ref new_tag(m); + expr_ref fml(m); + + if (!m_reach_facts.empty()) {last_tag = m_reach_facts.back()->tag();} + if (rf->is_init ()) + new_tag = mk_fresh_rf_tag(); + else + // side-effect: updates m_solver with rf + new_tag = to_app(extend_initial(rf->get())->get_arg(0)); + rf->set_tag(new_tag); + + // add to m_reach_facts + m_reach_facts.push_back (rf); + if (rf->is_init()) {m_rf_init_sz++;} + + // update m_reach_solver + if (last_tag) {fml = m.mk_or(m.mk_not(last_tag), rf->get(), rf->tag());} + else {fml = m.mk_or(rf->get(), rf->tag());} + m_reach_solver->assert_expr (fml); + TRACE ("spacer", tout << "updating reach ctx: " << fml << "\n";); + + // update solvers of other pred_transformers + // XXX wrap rf into a lemma to fit the API + lemma fake_lemma(m, fml, infty_level()); + // update users; reach facts are independent of levels + for (auto use : m_use) + use->add_lemma_from_child (*this, &fake_lemma, infty_level()); +} + +expr_ref pred_transformer::get_reachable() +{ + expr_ref res(m); + res = m.mk_false(); + + if (!m_reach_facts.empty()) { + expr_substitution sub(m); + expr_ref c(m), v(m); + for (unsigned i = 0, sz = sig_size(); i < sz; ++i) { + c = m.mk_const(pm.o2n(sig(i), 0)); + v = m.mk_var(i, sig(i)->get_range()); + sub.insert(c, v); + } + scoped_ptr rep = mk_expr_simp_replacer(m); + rep->set_substitution(&sub); + + expr_ref_vector args(m); + for (unsigned i = 0, sz = m_reach_facts.size (); i < sz; ++i) { + reach_fact *f; + f = m_reach_facts.get(i); + expr_ref r(m); + r = f->get(); + const ptr_vector &aux = f->aux_vars(); + if (!aux.empty()) { + // -- existentially quantify auxiliary variables + r = mk_exists (m, aux.size(), aux.c_ptr(), r); + // XXX not sure how this interacts with variable renaming later on. + // XXX For now, simply dissallow existentially quantified auxiliaries + NOT_IMPLEMENTED_YET(); + } + (*rep)(r); + + args.push_back (r); + } + res = mk_or(args); + } + return res; +} + +expr* pred_transformer::get_last_rf_tag () const +{return m_reach_facts.empty() ? nullptr : m_reach_facts.back()->tag();} + +expr_ref pred_transformer::get_cover_delta(func_decl* p_orig, int level) +{ + expr_ref result(m.mk_true(), m), v(m), c(m); + + expr_ref_vector lemmas (m); + m_frames.get_frame_lemmas (level == -1 ? infty_level() : level, lemmas); + if (!lemmas.empty()) { result = mk_and(lemmas); } + + // replace local constants by bound variables. + expr_substitution sub(m); + for (unsigned i = 0; i < sig_size(); ++i) { + c = m.mk_const(pm.o2n(sig(i), 0)); + v = m.mk_var(i, sig(i)->get_range()); + sub.insert(c, v); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(result); + + // adjust result according to model converter. + unsigned arity = m_head->get_arity(); + model_ref md = alloc(model, m); + if (arity == 0) { + md->register_decl(m_head, result); + } else { + func_interp* fi = alloc(func_interp, m, arity); + fi->set_else(result); + md->register_decl(m_head, fi); + } + model_converter_ref mc = ctx.get_model_converter(); + apply(mc, md); + if (p_orig->get_arity() == 0) { + result = md->get_const_interp(p_orig); + } else { + result = md->get_func_interp(p_orig)->get_interp(); + } + return result; +} + +/** + * get an origin summary used by this transformer in the given model + * level is the level at which may summaries are obtained + * oidx is the origin index of this predicate in the model + * must indicates whether a must or a may summary is requested + * + * returns an implicant of the summary + */ +expr_ref pred_transformer::get_origin_summary (model &mdl, + unsigned level, + unsigned oidx, + bool must, + const ptr_vector **aux) +{ + model::scoped_model_completion _sc_(mdl, false); + expr_ref_vector summary (m); + expr_ref v(m); + + if (!must) { // use may summary + summary.push_back (get_formulas(level)); + // -- no auxiliary variables in lemmas + *aux = nullptr; + } else { // find must summary to use + reach_fact *f = get_used_origin_rf(mdl, oidx); + summary.push_back (f->get ()); + *aux = &f->aux_vars (); + } + + SASSERT (!summary.empty ()); + + // -- convert to origin + for (unsigned i = 0; i < summary.size(); ++i) { + pm.formula_n2o (summary.get (i), v, oidx); + summary[i] = v; + } + + // bail out of if the model is insufficient + if (!mdl.is_true(summary)) return expr_ref(m); + + // -- pick an implicant + expr_ref_vector lits(m); + compute_implicant_literals (mdl, summary, lits); + return mk_and(lits); +} + + +void pred_transformer::add_cover(unsigned level, expr* property) +{ + // replace bound variables by local constants. + expr_ref result(property, m), v(m), c(m); + expr_substitution sub(m); + for (unsigned i = 0; i < sig_size(); ++i) { + c = m.mk_const(pm.o2n(sig(i), 0)); + v = m.mk_var(i, sig(i)->get_range()); + sub.insert(v, c); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(result); + TRACE("spacer", tout << "cover:\n" << mk_pp(result, m) << "\n";); + + // add the property. + expr_ref_vector lemmas(m); + flatten_and(result, lemmas); + for (unsigned i = 0, sz = lemmas.size(); i < sz; ++i) { + add_lemma(lemmas.get(i), level); + } +} + +void pred_transformer::propagate_to_infinity (unsigned level) +{m_frames.propagate_to_infinity (level);} + + + +/// \brief Returns true if the obligation is already blocked by current lemmas +bool pred_transformer::is_blocked (pob &n, unsigned &uses_level) +{ + ensure_level (n.level ()); + prop_solver::scoped_level _sl (*m_solver, n.level ()); + m_solver->set_core (nullptr); + m_solver->set_model (nullptr); + + expr_ref_vector post(m), _aux(m); + post.push_back (n.post ()); + // this only uses the lemmas at the current level + // transition relation is irrelevant + // XXX quic3: not all lemmas are asserted at the post-condition + lbool res = m_solver->check_assumptions (post, _aux, _aux, + 0, nullptr, 0); + if (res == l_false) { uses_level = m_solver->uses_level(); } + return res == l_false; +} + + +bool pred_transformer::is_qblocked (pob &n) { + // XXX currently disabled + return false; + params_ref p; + p.set_bool("arith.ignore_int", true); + p.set_bool("array.weak", true); + p.set_bool("mbqi", false); + scoped_ptr s; + s = mk_smt_solver(m, p, symbol::null); + s->updt_params(p); + // XXX force parameters to be set + s->push(); + s->pop(1); + + expr_ref_vector frame_lemmas(m); + m_frames.get_frame_geq_lemmas (n.level (), frame_lemmas); + + // assert all lemmas + bool has_quant = false; + for (unsigned i = 0, sz = frame_lemmas.size (); i < sz; ++i) + { + has_quant = has_quant || is_quantifier(frame_lemmas.get(i)); + s->assert_expr(frame_lemmas.get(i)); + } + if (!has_quant) return false; + + // assert cti + s->assert_expr(n.post()); + lbool res = s->check_sat(0, 0); + + // if (res == l_false) { + // expr_ref_vector core(m); + // solver->get_itp_core(core); + // expr_ref c(m); + // c = mk_and(core); + // STRACE("spacer.expand-add", tout << "core: " << mk_epp(c,m) << "\n";); + // } + return res == l_false; +} + + +void pred_transformer::mbp(app_ref_vector &vars, expr_ref &fml, model &mdl, + bool reduce_all_selects, bool force) { + scoped_watch _t_(m_mbp_watch); + qe_project(m, vars, fml, mdl, reduce_all_selects, use_native_mbp(), !force); +} + +// +// check if predicate transformer has a satisfiable predecessor state. +// returns either a satisfiable predecessor state or +// return a property that blocks state and is implied by the +// predicate transformer (or some unfolding of it). +// +lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, + model_ref* model, unsigned& uses_level, + bool& is_concrete, datalog::rule const*& r, + vector& reach_pred_used, + unsigned& num_reuse_reach) +{ + TRACE("spacer", + tout << "is-reachable: " << head()->get_name() << " level: " + << n.level() << " depth: " << n.depth () << "\n"; + tout << mk_pp(n.post(), m) << "\n";); + timeit _timer (is_trace_enabled("spacer_timeit"), + "spacer::pred_transformer::is_reachable", + verbose_stream ()); + + ensure_level(n.level()); + + // prepare the solver + prop_solver::scoped_level _sl(*m_solver, n.level()); + prop_solver::scoped_subset_core _sc (*m_solver, !n.use_farkas_generalizer ()); + prop_solver::scoped_weakness _sw(*m_solver, 0, + ctx.weak_abs() ? n.weakness() : UINT_MAX); + m_solver->set_core(core); + m_solver->set_model(model); + + expr_ref_vector post (m), reach_assumps (m); + post.push_back (n.post ()); + + // populate reach_assumps + if (n.level () > 0 && !m_all_init) { + for (auto &kv : m_pt_rules) { + datalog::rule const* r = &kv.m_value->rule(); + find_predecessors(*r, m_predicates); + if (m_predicates.empty()) {continue;} + for (unsigned i = 0; i < m_predicates.size(); i++) { + const pred_transformer &pt = + ctx.get_pred_transformer(m_predicates[i]); + if (pt.has_rfs()) { + expr_ref a(m); + pm.formula_n2o(pt.get_last_rf_tag(), a, i); + reach_assumps.push_back(m.mk_not (a)); + } else { + reach_assumps.push_back(m.mk_not (kv.m_value->tag())); + break; + } + } + } + } + + TRACE ("spacer", + if (!reach_assumps.empty ()) { + tout << "reach assumptions\n"; + for (unsigned i = 0; i < reach_assumps.size (); i++) { + tout << mk_pp (reach_assumps.get (i), m) << "\n"; + } + } + ); + + // check local reachability; + // result is either sat (with some reach assumps) or + // unsat (even with no reach assumps) + expr *bg = m_extend_lit.get (); + lbool is_sat = m_solver->check_assumptions (post, reach_assumps, + m_transition_clause, 1, &bg, 0); + + TRACE ("spacer", + if (!reach_assumps.empty ()) { + tout << "reach assumptions used\n"; + for (unsigned i = 0; i < reach_assumps.size (); i++) { + tout << mk_pp (reach_assumps.get (i), m) << "\n"; + } + } + ); + + if (is_sat == l_true || is_sat == l_undef) { + if (core) { core->reset(); } + if (model) { + r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach); + TRACE ("spacer", tout << "reachable " + << "is_concrete " << is_concrete << " rused: "; + for (unsigned i = 0, sz = reach_pred_used.size (); i < sz; ++i) + tout << reach_pred_used [i]; + tout << "\n";); + } + + return is_sat; + } + if (is_sat == l_false) { + SASSERT (reach_assumps.empty ()); + TRACE ("spacer", tout << "unreachable with lemmas\n"; + if (core) { + tout << "Core:\n"; + for (unsigned i = 0; i < core->size (); i++) { + tout << mk_pp (core->get(i), m) << "\n"; + } + } + ); + uses_level = m_solver->uses_level(); + return l_false; + } + UNREACHABLE(); + return l_undef; +} + +/// returns true if lemma is blocked by an existing model +bool pred_transformer::is_ctp_blocked(lemma *lem) { + if (!ctx.use_ctp()) {return false;} + + if (!lem->has_ctp()) {return false;} + scoped_watch _t_(m_ctp_watch); + + model_ref &ctp = lem->get_ctp(); + + // -- find rule of the ctp + const datalog::rule *r; + r = find_rule(*ctp); + if (r == nullptr) {return false;} + + // -- find predicates along the rule + find_predecessors(*r, m_predicates); + + // check if any lemma blocks the ctp model + for (unsigned i = 0, sz = m_predicates.size(); i < sz; ++i) { + pred_transformer &pt = ctx.get_pred_transformer(m_predicates[i]); + expr_ref lemmas(m), val(m); + lemmas = pt.get_formulas(lem->level()); + pm.formula_n2o(lemmas.get(), lemmas, i); + if (ctp->is_false(lemmas)) return false; + } + + // lem is blocked by ctp since none of the lemmas at the previous + // level block ctp + return true; +} + +bool pred_transformer::is_invariant(unsigned level, lemma* lem, + unsigned& solver_level, + expr_ref_vector* core) +{ + m_stats.m_num_is_invariant++; + if (is_ctp_blocked(lem)) { + m_stats.m_num_ctp_blocked++; + return false; + } + + expr_ref lemma_expr(m); + lemma_expr = lem->get_expr(); + + expr_ref_vector conj(m), aux(m); + expr_ref gnd_lemma(m); + + + if (!get_context().use_qlemmas() && !lem->is_ground()) { + app_ref_vector tmp(m); + ground_expr(to_quantifier(lemma_expr)->get_expr (), gnd_lemma, tmp); + lemma_expr = gnd_lemma.get(); + } + + conj.push_back(mk_not(m, lemma_expr)); + flatten_and (conj); + + prop_solver::scoped_level _sl(*m_solver, level); + prop_solver::scoped_subset_core _sc (*m_solver, true); + prop_solver::scoped_weakness _sw (*m_solver, 1, + ctx.weak_abs() ? lem->weakness() : UINT_MAX); + model_ref mdl; + model_ref *mdl_ref_ptr = nullptr; + if (ctx.use_ctp()) {mdl_ref_ptr = &mdl;} + m_solver->set_core(core); + m_solver->set_model(mdl_ref_ptr); + expr * bg = m_extend_lit.get (); + lbool r = m_solver->check_assumptions (conj, aux, m_transition_clause, + 1, &bg, 1); + if (r == l_false) { + solver_level = m_solver->uses_level (); + lem->reset_ctp(); + if (level < m_solver->uses_level()) {m_stats.m_num_lemma_level_jump++;} + SASSERT (level <= solver_level); + } + else if (r == l_true) { + // optionally remove unused symbols from the model + if (mdl_ref_ptr) {lem->set_ctp(*mdl_ref_ptr);} + } + else {lem->reset_ctp();} + + return r == l_false; +} + +bool pred_transformer::check_inductive(unsigned level, expr_ref_vector& state, + unsigned& uses_level, unsigned weakness) +{ + expr_ref_vector conj(m), core(m); + expr_ref states(m); + states = mk_and(state); + states = m.mk_not(states); + mk_assumptions(head(), states, conj); + prop_solver::scoped_level _sl(*m_solver, level); + prop_solver::scoped_subset_core _sc (*m_solver, true); + prop_solver::scoped_weakness _sw (*m_solver, 1, + ctx.weak_abs() ? weakness : UINT_MAX); + m_solver->set_core(&core); + m_solver->set_model (nullptr); + expr_ref_vector aux (m); + conj.push_back (m_extend_lit); + lbool res = m_solver->check_assumptions (state, aux, + m_transition_clause, + conj.size (), conj.c_ptr (), 1); + if (res == l_false) { + state.reset(); + state.append(core); + uses_level = m_solver->uses_level(); + } + TRACE ("core_array_eq", + tout << "check_inductive: " + << "states: " << mk_pp (states, m) + << " is: " << res << "\n" + << "with transition: " << mk_pp (m_transition, m) << "\n";); + return res == l_false; +} + +void pred_transformer::mk_assumptions(func_decl* head, expr* fml, + expr_ref_vector& result) +{ + expr_ref tmp1(m), tmp2(m); + for (auto& kv : m_pt_rules) { + expr* tag = kv.m_value->tag(); + datalog::rule const& r = kv.m_value->rule(); + find_predecessors(r, m_predicates); + for (unsigned i = 0; i < m_predicates.size(); i++) { + func_decl* d = m_predicates[i]; + if (d == head) { + tmp1 = m.mk_implies(tag, fml); + pm.formula_n2o(tmp1, tmp2, i); + result.push_back(tmp2); + } + } + } +} + +void pred_transformer::initialize(decl2rel const& pts) +{ + m_init = m.mk_false(); + m_transition = m.mk_true(); + init_rules(pts); + th_rewriter rw(m); + rw(m_transition); + rw(m_init); + + m_solver->assert_expr (m_transition); + m_solver->assert_expr (m_init, 0); + TRACE("spacer", + tout << "Initial state: " << mk_pp(m_init, m) << "\n"; + tout << "Transition: " << mk_pp(m_transition, m) << "\n";); + SASSERT(is_app(m_init)); + //m_reachable.add_init(to_app(m_init)); + + +} + +void pred_transformer::init_rfs () +{ + expr_ref_vector v(m); + reach_fact_ref fact; + + for (auto &kv : m_pt_rules) { + pt_rule &ptr = *kv.m_value; + const datalog::rule& r = ptr.rule(); + if (ptr.is_init()) { + fact = alloc(reach_fact, m, r, ptr.trans(), ptr.auxs(), true); + add_rf(fact.get()); + } + } +} + +void pred_transformer::init_rules(decl2rel const& pts) { + expr_ref_vector transitions(m), not_inits(m); + app_ref tag(m); + for (auto r : m_rules) { + init_rule(pts, *r); + } + + if (m_pt_rules.empty()) { + m_transition = m.mk_false(); + m_transition_clause.reset(); + } + else { + unsigned i = 0; + expr_ref_vector transitions(m); + m_transition_clause.push_back (m_extend_lit->get_arg(0)); + for (auto &kv : m_pt_rules) { + pt_rule &r = *kv.m_value; + std::string name = head()->get_name().str() + "__tr" + std::to_string(i); + tag = m.mk_const(symbol(name.c_str()), m.mk_bool_sort()); + m_pt_rules.set_tag(tag, r); + m_transition_clause.push_back(tag); + transitions.push_back(m.mk_implies(r.tag(), r.trans())); + if (!r.is_init()) {not_inits.push_back(m.mk_not(tag));} + ++i; + } + + if (!ctx.use_inc_clause()) { + transitions.push_back(mk_or(m_transition_clause)); + m_transition_clause.reset(); + } + m_transition = mk_and(transitions); + } + // mk init condition -- disables all non-initial transitions + m_init = mk_and(not_inits); + // no rule has uninterpreted tail + if (not_inits.empty ()) {m_all_init = true;} +} + +#ifdef Z3DEBUG +static bool is_all_non_null(app_ref_vector const& apps) { + for (auto *a : apps) if (!a) return false; + return true; +} +#endif + +void pred_transformer::init_rule(decl2rel const& pts, datalog::rule const& rule) { + scoped_watch _t_(m_initialize_watch); + + // Predicates that are variable representatives. Other predicates at + // positions the variables occur are made equivalent with these. + expr_ref_vector side(m); + app_ref_vector var_reprs(m); + ptr_vector aux_vars; + + unsigned ut_size = rule.get_uninterpreted_tail_size(); + unsigned t_size = rule.get_tail_size(); + SASSERT(ut_size <= t_size); + init_atom(pts, rule.get_head(), var_reprs, side, UINT_MAX); + for (unsigned i = 0; i < ut_size; ++i) { + if (rule.is_neg_tail(i)) { + throw default_exception("SPACER does not support " + "negated predicates in rule tails"); + } + init_atom(pts, rule.get_tail(i), var_reprs, side, i); + } + // -- substitute free variables + expr_ref trans(m); + { + expr_ref_vector tail(m); + for (unsigned i = ut_size; i < t_size; ++i) + tail.push_back(rule.get_tail(i)); + trans= mk_and (tail); + + ground_free_vars(trans, var_reprs, aux_vars, ut_size == 0); + SASSERT(is_all_non_null(var_reprs)); + + expr_ref tmp(m); + var_subst(m, false)(trans, var_reprs.size (), + (expr*const*)var_reprs.c_ptr(), tmp); + flatten_and (tmp, side); + trans = mk_and(side); + side.reset (); + } + + // rewrite and simplify + th_rewriter rw(m); + rw(trans); + if (ctx.blast_term_ite()) {blast_term_ite(trans, 3); rw(trans);} + TRACE("spacer_init_rule", tout << mk_pp(trans, m) << "\n";); + + // allow quantifiers in init rule + SASSERT(ut_size == 0 || is_ground(trans)); + if (!m.is_false(trans)) { + pt_rule &ptr = m_pt_rules.mk_rule(m, rule); + ptr.set_trans(trans); + ptr.set_auxs(aux_vars); + ptr.set_reps(var_reprs); + } + + // TRACE("spacer", + // tout << rule.get_decl()->get_name() << "\n"; + // tout << var_reprs << "\n";); +} + + +// create constants for free variables in tail. +void pred_transformer::ground_free_vars(expr* e, app_ref_vector& vars, + ptr_vector& aux_vars, bool is_init) { + expr_free_vars fv; + fv(e); + + while (vars.size() < fv.size()) {vars.push_back(nullptr);} + + for (unsigned i = 0; i < fv.size(); ++i) { + if (fv[i] && !vars[i].get()) { + // AG: is it useful to make names unique across rules? + app_ref v(m); + v = m.mk_fresh_const("aux", fv[i]); + v = m.mk_const (pm.get_n_pred(v->get_decl ())); + vars[i] = v; + aux_vars.push_back(v); + } + } + +} + +// create names for variables used in relations. +void pred_transformer::init_atom(decl2rel const &pts, app *atom, + app_ref_vector &var_reprs, + expr_ref_vector &side, unsigned tail_idx) { + unsigned arity = atom->get_num_args(); + func_decl* head = atom->get_decl(); + pred_transformer& pt = *pts.find(head); + for (unsigned i = 0; i < arity; i++) { + app_ref rep(m); + + if (tail_idx == UINT_MAX) { + rep = m.mk_const(pm.o2n(pt.sig(i), 0)); + } else { + rep = m.mk_const(pm.o2o(pt.sig(i), 0, tail_idx)); + } + + expr * arg = atom->get_arg(i); + if (is_var(arg)) { + var * v = to_var(arg); + unsigned var_idx = v->get_idx(); + if (var_idx >= var_reprs.size()) { + var_reprs.resize(var_idx+1); + } + expr * repr = var_reprs[var_idx].get(); + if (repr) { + side.push_back(m.mk_eq(rep, repr)); + } else { + var_reprs[var_idx] = rep; + } + } else { + SASSERT(is_app(arg)); + side.push_back(m.mk_eq(rep, arg)); + } + } +} + +void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r) +{ + if (lvl == 0) {r.push_back(m_init);} + else { + r.push_back(m_transition); + if (!m_transition_clause.empty()) { + expr_ref c(m); + c = mk_or(m_transition_clause); + r.push_back(c); + } + } + for (unsigned i = 0; i < rules().size(); ++i) { + add_premises(pts, lvl, *rules()[i], r); + } +} + +void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, + datalog::rule& rule, expr_ref_vector& r) +{ + find_predecessors(rule, m_predicates); + for (unsigned i = 0; i < m_predicates.size(); ++i) { + expr_ref tmp(m); + func_decl* head = m_predicates[i]; + pred_transformer& pt = *pts.find(head); + expr_ref inv = pt.get_formulas(lvl); + if (!m.is_true(inv)) { + pm.formula_n2o(inv, tmp, i, true); + r.push_back(tmp); + } + } +} + +void pred_transformer::inherit_lemmas(pred_transformer& other) +{m_frames.inherit_frames (other.m_frames);} + +app* pred_transformer::extend_initial (expr *e) +{ + // create fresh extend literal + app_ref v(m); + std::stringstream name; + name << m_head->get_name() << "_ext"; + v = m.mk_fresh_const (name.str ().c_str (), + m.mk_bool_sort ()); + v = m.mk_const (pm.get_n_pred (v->get_decl ())); + + expr_ref ic(m); + + // -- extend the initial condition + ic = m.mk_or (m_extend_lit, e, v); + m_solver->assert_expr (ic); + + // -- remember the new extend literal + m_extend_lit = m.mk_not (v); + + return m_extend_lit; +} + + +/// \brief Update a given solver with all constraints representing +/// this pred_transformer +void pred_transformer::updt_solver(prop_solver *solver) { + + solver->assert_expr(m_transition); + solver->assert_expr(m_init, 0); + + // -- facts derivable at the head + expr_ref last_tag(m); + last_tag = m_extend_lit0; + for (auto *rf : m_reach_facts) { + if (rf->is_init()) continue; // already in m_init + solver->assert_expr(m.mk_or(last_tag, rf->get(), rf->tag())); + last_tag = m.mk_not(rf->tag()); + } + SASSERT(last_tag == m_extend_lit); + + // -- lemmas + app_ref_vector _unused(m); + expr_ref_vector fmls(m); + // -- assert lemmas + for (auto *u : m_frames.lemmas()) { + // instances + u->mk_insts(fmls); + + // extra ground instance + if (!u->is_ground()) { + expr_ref gnd(m); + ground_expr(u->get_expr(), gnd, _unused); + fmls.push_back(gnd); + } + + // (quantified) lemma + if (u->is_ground() || get_context().use_qlemmas()) + fmls.push_back(u->get_expr()); + + // send to solver + if (is_infty_level(u->level())) + solver->assert_exprs(fmls); + else { + for (unsigned i = 0; i <= u->level(); ++i) + solver->assert_exprs(fmls, i); + } + fmls.reset(); + } + + // -- lemmas and rfs from other predicates + for (auto &kv : m_pt_rules) { + const datalog::rule &r = kv.m_value->rule(); + find_predecessors(r, m_predicates); + if (m_predicates.empty()) continue; + + for (unsigned i = 0, sz = m_predicates.size(); i < sz; ++i) { + const pred_transformer &pt = ctx.get_pred_transformer(m_predicates[i]); + // assert lemmas of pt + updt_solver_with_lemmas(solver, pt, to_app(kv.m_value->tag()), i); + // assert rfs of pt + update_solver_with_rfs(solver, pt, to_app(kv.m_value->tag()), i); + } + } +} + +void pred_transformer::updt_solver_with_lemmas(prop_solver *solver, + const pred_transformer &pt, + app* rule_tag, unsigned pos) { + app_ref_vector _unused(m); + expr_ref_vector fmls(m); + for (auto *u : pt.m_frames.lemmas()) { + expr_ref e(m), gnd(m); + e = u->get_expr(); + pm.formula_n2o(e, e, pos); + u->mk_insts(fmls, e); + + if (!u->is_ground()) { + // special ground instance + ground_expr(u->get_expr(), gnd, _unused); + pm.formula_n2o(gnd, gnd, pos); + fmls.push_back(gnd); + } + + // quantified formula + if (u->is_ground() || get_context().use_qlemmas()) + fmls.push_back(e); + + // add tag + for (unsigned i = 0, sz = fmls.size(); i < sz; ++i) + fmls.set(i, m.mk_implies(rule_tag, fmls.get(i))); + + // send to solver + if (is_infty_level(u->level())) + solver->assert_exprs(fmls); + else { + for (unsigned i = 1, end = next_level(u->level()); i <= end; ++i) + solver->assert_exprs(fmls, i); + } + fmls.reset(); + } +} + +void pred_transformer::update_solver_with_rfs(prop_solver *solver, + const pred_transformer &pt, + app *rule_tag, unsigned pos) { + expr_ref not_rule_tag(m); + not_rule_tag = m.mk_not(rule_tag); + + expr_ref last_tag(m); + for (auto *rf : pt.m_reach_facts) { + expr_ref e(m); + if (!last_tag) { + e = m.mk_or(m.mk_not(rule_tag), rf->get(), rf->tag()); + } + else { + expr *args[4] = { not_rule_tag, last_tag, rf->get(), rf->tag() }; + e = m.mk_or(4, args); + } + last_tag = m.mk_not(rf->tag()); + pm.formula_n2o(e.get(), e, pos); + solver->assert_expr(e); + } +} + +/// pred_transformer::frames + + +bool pred_transformer::frames::add_lemma(lemma *new_lemma) +{ + TRACE("spacer", tout << "add-lemma: " << pp_level(new_lemma->level()) << " " + << m_pt.head()->get_name() << " " + << mk_pp(new_lemma->get_expr(), m_pt.get_ast_manager()) << "\n";); + + unsigned i = 0; + for (auto *old_lemma : m_lemmas) { + if (old_lemma->get_expr() == new_lemma->get_expr()) { + m_pt.get_context().new_lemma_eh(m_pt, new_lemma); + + // register existing lemma with the pob + if (new_lemma->has_pob()) { + pob_ref &pob = new_lemma->get_pob(); + if (!pob->lemmas().contains(old_lemma)) + pob->add_lemma(old_lemma); + } + + // extend bindings if needed + if (!new_lemma->get_bindings().empty()) { + old_lemma->add_binding(new_lemma->get_bindings()); + } + // if the lemma is at a higher level, skip it, + if (old_lemma->level() >= new_lemma->level()) { TRACE("spacer", tout << "Already at a higher level: " - << pp_level(m_lemmas [i]->level()) << "\n";); + << pp_level(old_lemma->level()) << "\n";); + // but, since the instances might be new, assert the + // instances that have been copied into m_lemmas[i] + if (!new_lemma->get_bindings().empty()) { + m_pt.add_lemma_core(old_lemma, true); + } + if (is_infty_level(old_lemma->level())) { + old_lemma->bump(); + if (old_lemma->get_bumped() >= 100) { + IF_VERBOSE(1, verbose_stream() << "Adding lemma to oo " + << old_lemma->get_bumped() << " " + << mk_pp(old_lemma->get_expr(), + m_pt.get_ast_manager()) << "\n";); + throw default_exception("Stuck on a lemma"); + } + } + // no new lemma added return false; } // update level of the existing lemma - m_lemmas [i]->set_level(lem->level()); + old_lemma->set_level(new_lemma->level()); // assert lemma in the solver - m_pt.add_lemma_core(m_lemmas[i]); + m_pt.add_lemma_core(old_lemma, false); // move the lemma to its new place to maintain sortedness - for (unsigned j = i; (j + 1) < sz && m_lt(m_lemmas [j + 1], m_lemmas[j]); ++j) { + unsigned sz = m_lemmas.size(); + for (unsigned j = i; + (j + 1) < sz && m_lt(m_lemmas[j + 1], m_lemmas[j]); ++j) { m_lemmas.swap (j, j+1); } - return true; } + i++; } - // did not find, create new lemma - m_lemmas.push_back(lem); + // new_lemma is really new + m_lemmas.push_back(new_lemma); + // XXX because m_lemmas is reduced, keep secondary vector of all lemmas + // XXX so that pob can refer to its lemmas without creating reference cycles + m_pinned_lemmas.push_back(new_lemma); m_sorted = false; - m_pt.add_lemma_core(lem); + m_pt.add_lemma_core(new_lemma); + + if (new_lemma->has_pob()) {new_lemma->get_pob()->add_lemma(new_lemma);} + + if (!new_lemma->external()) { + m_pt.get_context().new_lemma_eh(m_pt, new_lemma); + } return true; } @@ -1322,20 +1996,18 @@ bool pred_transformer::frames::propagate_to_next_level (unsigned level) m_pt.ensure_level (tgt_level); for (unsigned i = 0, sz = m_lemmas.size(); i < sz && m_lemmas [i]->level() <= level;) { - if (m_lemmas [i]->level () < level) - {++i; continue;} - + if (m_lemmas [i]->level () < level) {++i; continue;} unsigned solver_level; - expr * curr = m_lemmas [i]->get_expr (); - if (m_pt.is_invariant(tgt_level, curr, solver_level)) { + if (m_pt.is_invariant(tgt_level, m_lemmas.get(i), solver_level)) { m_lemmas [i]->set_level (solver_level); - m_pt.add_lemma_core (m_lemmas [i]); + m_pt.add_lemma_core (m_lemmas.get(i)); // percolate the lemma up to its new place for (unsigned j = i; (j+1) < sz && m_lt (m_lemmas[j+1], m_lemmas[j]); ++j) { m_lemmas.swap(j, j + 1); } + ++m_pt.m_stats.m_num_propagations; } else { all = false; ++i; @@ -1352,13 +2024,13 @@ void pred_transformer::frames::simplify_formulas () // ensure that the lemmas are sorted sort(); - ast_manager &m = m_pt.get_ast_manager (); + ast_manager &m = m_pt.get_ast_manager(); - tactic_ref simplifier = mk_unit_subsumption_tactic (m); + tactic_ref simplifier = mk_unit_subsumption_tactic(m); lemma_ref_vector new_lemmas; - unsigned lemmas_size = m_lemmas.size (); - goal_ref g (alloc (goal, m, false, false, false)); + unsigned lemmas_size = m_lemmas.size(); + goal_ref g(alloc (goal, m, false, false, false)); unsigned j = 0; // for every frame + infinity frame @@ -1426,6 +2098,11 @@ void pred_transformer::frames::simplify_formulas () << mk_pp(m_lemmas[n]->get_expr(), m) << "\n"; } + + verbose_stream() << "Simplified goal is:\n"; + for (unsigned k = 0; k < r->size(); ++k) + verbose_stream() << k << ": " + << mk_pp(r->form(k), m) << "\n"; } ENSURE(found); SASSERT(found); @@ -1443,11 +2120,13 @@ void pred_transformer::frames::simplify_formulas () } } +/// pred_transformer::pobs + pob* pred_transformer::pobs::mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, app_ref_vector const &b) { - if (!m_pt.ctx.get_params().spacer_reuse_pobs()) { + if (!m_pt.ctx.reuse_pobs()) { pob* n = alloc(pob, parent, m_pt, level, depth); n->set_post(post, b); return n; @@ -1483,418 +2162,38 @@ pob* pred_transformer::pobs::mk_pob(pob *parent, return n; } -app* pred_transformer::extend_initial (expr *e) -{ - // create fresh extend literal - app_ref v(m); - std::stringstream name; - name << m_head->get_name() << "_ext"; - v = m.mk_fresh_const (name.str ().c_str (), - m.mk_bool_sort ()); - v = m.mk_const (pm.get_n_pred (v->get_decl ())); - expr_ref ic(m); - // -- extend the initial condition - ic = m.mk_or (m_extend_lit, e, v); - m_solver.assert_expr (ic); - - // -- remember the new extend literal - m_extend_lit = m.mk_not (v); - - return m_extend_lit; -} - - -// ---------------- -// derivation - -derivation::derivation (pob& parent, datalog::rule const& rule, - expr *trans, app_ref_vector const &evars) : - m_parent (parent), - m_rule (rule), - m_premises (), - m_active (0), - m_trans (trans, m_parent.get_ast_manager ()), - m_evars (evars) {} - -derivation::premise::premise (pred_transformer &pt, unsigned oidx, - expr *summary, bool must, - const ptr_vector *aux_vars) : - m_pt (pt), m_oidx (oidx), - m_summary (summary, pt.get_ast_manager ()), m_must (must), - m_ovars (pt.get_ast_manager ()) -{ - - ast_manager &m = m_pt.get_ast_manager (); - manager &sm = m_pt.get_manager (); - - unsigned sig_sz = m_pt.head ()->get_arity (); - for (unsigned i = 0; i < sig_sz; ++i) - { m_ovars.push_back(m.mk_const(sm.o2o(pt.sig(i), 0, m_oidx))); } - - if (aux_vars) - for (unsigned i = 0, sz = aux_vars->size (); i < sz; ++i) - { m_ovars.push_back(m.mk_const(sm.n2o(aux_vars->get(i)->get_decl(), m_oidx))); } -} - -derivation::premise::premise (const derivation::premise &p) : - m_pt (p.m_pt), m_oidx (p.m_oidx), m_summary (p.m_summary), m_must (p.m_must), - m_ovars (p.m_ovars) {} - -/// \brief Updated the summary. -/// The new summary is over n-variables. -void derivation::premise::set_summary (expr * summary, bool must, - const ptr_vector *aux_vars) -{ - ast_manager &m = m_pt.get_ast_manager (); - manager &sm = m_pt.get_manager (); - unsigned sig_sz = m_pt.head ()->get_arity (); - - m_must = must; - sm.formula_n2o (summary, m_summary, m_oidx); - - m_ovars.reset (); - for (unsigned i = 0; i < sig_sz; ++i) - { m_ovars.push_back(m.mk_const(sm.o2o(m_pt.sig(i), 0, m_oidx))); } - - if (aux_vars) - for (unsigned i = 0, sz = aux_vars->size (); i < sz; ++i) - m_ovars.push_back (m.mk_const (sm.n2o (aux_vars->get (i)->get_decl (), - m_oidx))); -} - - -void derivation::add_premise (pred_transformer &pt, - unsigned oidx, - expr* summary, - bool must, - const ptr_vector *aux_vars) -{m_premises.push_back (premise (pt, oidx, summary, must, aux_vars));} - - - -pob *derivation::create_first_child (model_evaluator_util &mev) -{ - if (m_premises.empty()) { return nullptr; } - m_active = 0; - return create_next_child(mev); -} - -pob *derivation::create_next_child (model_evaluator_util &mev) -{ - timeit _timer (is_trace_enabled("spacer_timeit"), - "spacer::derivation::create_next_child", - verbose_stream ()); - - ast_manager &m = get_ast_manager (); - expr_ref_vector summaries (m); - app_ref_vector vars (m); - - bool use_native_mbp = get_context ().use_native_mbp (); - bool ground = get_context ().use_ground_cti (); - // -- find first may premise - while (m_active < m_premises.size() && m_premises[m_active].is_must()) { - summaries.push_back (m_premises[m_active].get_summary ()); - vars.append (m_premises[m_active].get_ovars ()); - ++m_active; - } - if (m_active >= m_premises.size()) { return nullptr; } - - // -- update m_trans with the pre-image of m_trans over the must summaries - summaries.push_back (m_trans); - m_trans = get_manager ().mk_and (summaries); - summaries.reset (); - - if (!vars.empty()) { - timeit _timer1 (is_trace_enabled("spacer_timeit"), - "create_next_child::qproject1", - verbose_stream ()); - qe_project (m, vars, m_trans, mev.get_model (), true, use_native_mbp, !ground); - //qe::reduce_array_selects (*mev.get_model (), m_trans); - // remember variables that need to be existentially quantified - m_evars.append (vars); - } - - if (!mev.is_true (m_premises[m_active].get_summary())) { - IF_VERBOSE(1, verbose_stream() << "Summary unexpectendly not true\n";); - return nullptr; - } - - - // create the post condition by compute post-image over summaries - // that precede currently active premise - vars.reset (); - for (unsigned i = m_active + 1; i < m_premises.size(); ++i) { - summaries.push_back (m_premises [i].get_summary ()); - vars.append (m_premises [i].get_ovars ()); - } - summaries.push_back (m_trans); - - expr_ref post(m); - post = get_manager ().mk_and (summaries); - summaries.reset (); - if (!vars.empty()) { - timeit _timer2 (is_trace_enabled("spacer_timeit"), - "create_next_child::qproject2", - verbose_stream ()); - qe_project (m, vars, post, mev.get_model (), true, use_native_mbp, !ground); - //qe::reduce_array_selects (*mev.get_model (), post); - - // remember variables that need to be existentially quantified - m_evars.append (vars); - } - - get_manager ().formula_o2n (post.get (), post, - m_premises [m_active].get_oidx (), m_evars.empty()); - - - /* The level and depth are taken from the parent, not the sibling. - The reasoning is that the sibling has not been checked before, - and lower level is a better starting point. */ - pob *n = m_premises[m_active].pt().mk_pob(&m_parent, - prev_level (m_parent.level ()), - m_parent.depth (), post, m_evars); - - IF_VERBOSE (1, verbose_stream () - << "\n\tcreate_child: " << n->pt ().head ()->get_name () - << " (" << n->level () << ", " << n->depth () << ") " - << (n->use_farkas_generalizer () ? "FAR " : "SUB ") - << n->post ()->get_id (); - verbose_stream().flush ();); - return n; -} - -pob *derivation::create_next_child () -{ - if (m_active + 1 >= m_premises.size()) { return nullptr; } - - bool use_native_mbp = get_context ().use_native_mbp (); - bool ground = get_context ().use_ground_cti (); - - // update the summary of the active node to some must summary - - // construct a new model consistent with the must summary of m_active premise - pred_transformer &pt = m_premises[m_active].pt (); - model_ref model; - - ast_manager &m = get_ast_manager (); - manager &pm = get_manager (); - - expr_ref_vector summaries (m); - - for (unsigned i = m_active + 1; i < m_premises.size (); ++i) - { summaries.push_back(m_premises [i].get_summary()); } - - // -- orient transition relation towards m_active premise - expr_ref active_trans (m); - pm.formula_o2n (m_trans, active_trans, - m_premises[m_active].get_oidx (), false); - summaries.push_back (active_trans); - - // if not true, bail out, the must summary of m_active is not strong enough - // this is possible if m_post was weakened for some reason - if (!pt.is_must_reachable(pm.mk_and(summaries), &model)) { return nullptr; } - - model_evaluator_util mev (m); - mev.set_model (*model); - // find must summary used - - reach_fact *rf = pt.get_used_reach_fact (mev, true); - - // get an implicant of the summary - expr_ref_vector u(m), lits (m); - u.push_back (rf->get ()); - compute_implicant_literals (mev, u, lits); - expr_ref v(m); - v = pm.mk_and (lits); - - // XXX The summary is not used by anyone after this point - m_premises[m_active].set_summary (v, true, &(rf->aux_vars ())); - - - /** HACK: needs a rewrite - * compute post over the new must summary this must be done here - * because the must summary is currently described over new - * variables. However, we store it over old-variables, but we do - * not update the model. So we must get rid of all of the - * new-variables at this point. - */ - { - pred_transformer &pt = m_premises[m_active].pt (); - app_ref_vector vars (m); - - summaries.reset (); - summaries.push_back (v); - summaries.push_back (active_trans); - m_trans = pm.mk_and (summaries); - - // variables to eliminate - vars.append (rf->aux_vars ().size (), rf->aux_vars ().c_ptr ()); - for (unsigned i = 0, sz = pt.head ()->get_arity (); i < sz; ++i) - { vars.push_back(m.mk_const(pm.o2n(pt.sig(i), 0))); } - - if (!vars.empty ()) { - qe_project (m, vars, m_trans, mev.get_model (), true, use_native_mbp, - !ground); - // keep track of implicitly quantified variables - m_evars.append (vars); - } - } - - m_active++; - - return create_next_child (mev); -} - -pob::pob (pob* parent, pred_transformer& pt, - unsigned level, unsigned depth, bool add_to_parent): - m_ref_count (0), - m_parent (parent), m_pt (pt), - m_post (m_pt.get_ast_manager ()), - m_binding(m_pt.get_ast_manager()), - m_new_post (m_pt.get_ast_manager ()), - m_level (level), m_depth (depth), - m_open (true), m_use_farkas (true), m_weakness(0) { - if(add_to_parent && m_parent) { - m_parent->add_child(*this); - } -} - - -void pob::set_post(expr* post) { - app_ref_vector b(get_ast_manager()); - set_post(post, b); -} - -void pob::set_post(expr* post, app_ref_vector const &b) { - normalize(post, m_post, - m_pt.get_context().get_params().spacer_simplify_pob(), - m_pt.get_context().get_params().spacer_use_eqclass()); - - m_binding.reset(); - if (b.empty()) return; - - m_binding.append(b); - - std::sort (m_binding.c_ptr(), m_binding.c_ptr() + m_binding.size(), ast_lt_proc()); - - // skolemize implicit existential quantifier - ast_manager &m = get_ast_manager(); - app_ref_vector pinned(m); - - expr_safe_replace sub(m); - for (unsigned i = 0, sz = m_binding.size(); i < sz; ++i) { - expr* e; - - e = m_binding.get(i); - pinned.push_back (mk_zk_const (m, i, get_sort(e))); - sub.insert (e, pinned.back()); - } - sub(m_post); -} - -void pob::inherit(pob const &p) { - SASSERT(m_parent == p.m_parent); - SASSERT(&m_pt == &p.m_pt); - SASSERT(m_post == p.m_post); - SASSERT(!m_new_post); - - m_binding.reset(); - m_binding.append(p.m_binding); - - m_level = p.m_level; - m_depth = p.m_depth; - m_open = p.m_open; - m_use_farkas = p.m_use_farkas; - m_weakness = p.m_weakness; - - m_derivation = nullptr; -} - -void pob::clean () { - if(m_new_post) { - m_post = m_new_post; - m_new_post.reset(); - } -} - -void pob::close () { - if(!m_open) { return; } - - reset (); - m_open = false; - for (unsigned i = 0, sz = m_kids.size (); i < sz; ++i) - { m_kids [i]->close(); } -} - -void pob::get_skolems(app_ref_vector &v) { - for (unsigned i = 0, sz = m_binding.size(); i < sz; ++i) { - expr* e; - - e = m_binding.get(i); - v.push_back (mk_zk_const (get_ast_manager(), i, get_sort(e))); - } -} - - - -// ---------------- -// pob_queue - -pob* pob_queue::top () -{ - /// nothing in the queue - if (m_obligations.empty()) { return nullptr; } - /// top queue element is above max level - if (m_obligations.top()->level() > m_max_level) { return nullptr; } - /// top queue element is at the max level, but at a higher than base depth - if (m_obligations.top ()->level () == m_max_level && - m_obligations.top()->depth() > m_min_depth) { return nullptr; } - - /// there is something good in the queue - return m_obligations.top ().get (); -} - -void pob_queue::set_root(pob& root) -{ - m_root = &root; - m_max_level = root.level (); - m_min_depth = root.depth (); - reset(); -} - -pob_queue::~pob_queue() {} - -void pob_queue::reset() -{ - while (!m_obligations.empty()) { m_obligations.pop(); } - if (m_root) { m_obligations.push(m_root); } -} // ---------------- // context -context::context(fixedpoint_params const& params, - ast_manager& m) : +context::context(fp_params const& params, ast_manager& m) : m_params(params), m(m), m_context(nullptr), - m_pm(params.pdr_max_num_contexts(), m), + m_pm(m), m_query_pred(m), m_query(nullptr), m_pob_queue(), m_last_result(l_undef), m_inductive_lvl(0), m_expanded_lvl(0), - m_use_native_mbp(params.spacer_native_mbp ()), - m_ground_cti (params.spacer_ground_cti ()), - m_instantiate (params.spacer_instantiate ()), - m_use_qlemmas (params.spacer_qlemmas ()), - m_weak_abs(params.spacer_weak_abs()), - m_use_restarts(params.spacer_restarts()), - m_restart_initial_threshold(params.spacer_restart_initial_threshold()) -{} + m_json_marshaller(this) { + ref pool0_base = + mk_smt_solver(m, params_ref::get_empty(), symbol::null); + ref pool1_base = + mk_smt_solver(m, params_ref::get_empty(), symbol::null); + ref pool2_base = + mk_smt_solver(m, params_ref::get_empty(), symbol::null); + + unsigned max_num_contexts = params.spacer_max_num_contexts(); + m_pool0 = alloc(solver_pool, pool0_base.get(), max_num_contexts); + m_pool1 = alloc(solver_pool, pool1_base.get(), max_num_contexts); + m_pool2 = alloc(solver_pool, pool2_base.get(), max_num_contexts); + + updt_params(); +} context::~context() { @@ -1902,14 +2201,60 @@ context::~context() reset(); } +void context::updt_params() { + m_random.set_seed(m_params.spacer_random_seed()); + m_children_order = static_cast(m_params.spacer_order_children()); + m_simplify_pob = m_params.spacer_simplify_pob(); + m_use_euf_gen = m_params.spacer_use_euf_gen(); + m_use_ctp = m_params.spacer_ctp(); + m_use_inc_clause = m_params.spacer_use_inc_clause(); + m_blast_term_ite = m_params.spacer_blast_term_ite(); + m_reuse_pobs = m_params.spacer_reuse_pobs(); + m_use_ind_gen = m_params.spacer_use_inductive_generalizer(); + m_use_array_eq_gen = m_params.spacer_use_array_eq_generalizer(); + m_validate_lemmas = m_params.spacer_validate_lemmas(); + m_max_level = m_params.spacer_max_level (); + m_use_propagate = m_params.spacer_propagate (); + m_reset_obligation_queue = m_params.spacer_reset_pob_queue(); + m_push_pob = m_params.spacer_push_pob(); + m_push_pob_max_depth = m_params.spacer_push_pob_max_depth(); + m_use_lemma_as_pob = m_params.spacer_use_lemma_as_cti(); + m_elim_aux = m_params.spacer_elim_aux(); + m_reach_dnf = m_params.spacer_reach_dnf(); + m_use_derivations = m_params.spacer_use_derivations(); + m_validate_result = m_params.validate(); + m_use_eq_prop = m_params.spacer_eq_prop(); + m_ground_pob = m_params.spacer_ground_pobs(); + m_q3_qgen = m_params.spacer_q3_use_qgen(); + m_use_gpdr = m_params.spacer_gpdr(); + m_simplify_formulas_pre = m_params.spacer_simplify_lemmas_pre(); + m_simplify_formulas_post = m_params.spacer_simplify_lemmas_post(); + m_use_native_mbp = m_params.spacer_native_mbp (); + m_instantiate = m_params.spacer_q3_instantiate (); + m_use_qlemmas = m_params.spacer_q3(); + m_weak_abs = m_params.spacer_weak_abs(); + m_use_restarts = m_params.spacer_restarts(); + m_restart_initial_threshold = m_params.spacer_restart_initial_threshold(); + m_pdr_bfs = m_params.spacer_gpdr_bfs(); + + if (m_use_gpdr) { + // set options to be compatible with GPDR + m_weak_abs = false; + m_push_pob = false; + m_use_qlemmas = false; + m_ground_pob = true; + m_reset_obligation_queue = false; + m_use_derivations = false; + m_use_lemma_as_pob = false; + } +} + + void context::reset() { TRACE("spacer", tout << "\n";); m_pob_queue.reset(); - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - dealloc(it->m_value); - } + for (auto &entry: m_rels) {dealloc(entry.m_value);} m_rels.reset(); m_query = nullptr; m_last_result = l_undef; @@ -1920,23 +2265,22 @@ void context::init_rules(datalog::rule_set& rules, decl2rel& rels) { scoped_watch _t_(m_init_rules_watch); m_context = &rules.get_context(); + // Allocate collection of predicate transformers - datalog::rule_set::decl2rules::iterator dit = rules.begin_grouped_rules(), dend = rules.end_grouped_rules(); - decl2rel::obj_map_entry* e; - for (; dit != dend; ++dit) { + for (auto dit = rules.begin_grouped_rules(), + dend = rules.end_grouped_rules(); dit != dend; ++dit) { func_decl* pred = dit->m_key; TRACE("spacer", tout << mk_pp(pred, m) << "\n";); SASSERT(!rels.contains(pred)); - e = rels.insert_if_not_there2(pred, alloc(pred_transformer, *this, - get_manager(), pred)); + auto *e = rels.insert_if_not_there2(pred, alloc(pred_transformer, *this, + get_manager(), pred)); datalog::rule_vector const& pred_rules = *dit->m_value; - for (unsigned i = 0; i < pred_rules.size(); ++i) { - e->get_data().m_value->add_rule(pred_rules[i]); - } + for (auto rule : pred_rules) {e->get_data().m_value->add_rule(rule);} } - datalog::rule_set::iterator rit = rules.begin(), rend = rules.end(); - for (; rit != rend; ++rit) { - datalog::rule* r = *rit; + + // Allocate predicate transformers for predicates that are used + // but don't have rules + for (auto *r : rules) { pred_transformer* pt; unsigned utz = r->get_uninterpreted_tail_size(); for (unsigned i = 0; i < utz; ++i) { @@ -1947,51 +2291,60 @@ void context::init_rules(datalog::rule_set& rules, decl2rel& rels) } } } + // Initialize use list dependencies - decl2rel::iterator it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - func_decl* pred = it->m_key; - pred_transformer* pt = it->m_value, *pt_user; - obj_hashtable const& deps = rules.get_dependencies().get_deps(pred); - obj_hashtable::iterator itf = deps.begin(), endf = deps.end(); - for (; itf != endf; ++itf) { - TRACE("spacer", tout << mk_pp(pred, m) << " " << mk_pp(*itf, m) << "\n";); - pt_user = rels.find(*itf); + for (auto &entry : rels) { + func_decl* pred = entry.m_key; + pred_transformer* pt = entry.m_value, *pt_user = nullptr; + for (auto dep : rules.get_dependencies().get_deps(pred)) { + TRACE("spacer", tout << mk_pp(pred, m) << " " << mk_pp(dep, m) << "\n";); + rels.find(dep, pt_user); pt_user->add_use(pt); } } // Initialize the predicate transformers. - it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - pred_transformer& rel = *it->m_value; - rel.initialize(rels); - TRACE("spacer", rel.display(tout); ); + for (auto &entry : rels) { + pred_transformer* rel = entry.m_value; + rel->initialize(rels); + TRACE("spacer", rel->display(tout); ); } // initialize reach facts - it = rels.begin (), end = rels.end (); - for (; it != end; ++it) - { it->m_value->init_reach_facts(); } + for (auto &entry : rels) {entry.m_value->init_rfs();} +} + +void context::inherit_lemmas(const decl2rel &rels) { + for (auto &entry : rels) { + pred_transformer *pt = nullptr; + if (m_rels.find(entry.m_key, pt)) { + entry.m_value->inherit_lemmas(*pt); + } + } } void context::update_rules(datalog::rule_set& rules) { decl2rel rels; - init_lemma_generalizers(rules); + // SMT params must be set before any expression is asserted to any + // solver + init_global_smt_params(); + // constructs new pred transformers and asserts trans and init init_rules(rules, rels); - decl2rel::iterator it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - pred_transformer* pt = nullptr; - if (m_rels.find(it->m_key, pt)) { - it->m_value->inherit_properties(*pt); - } - } + // inherits lemmas from m_rels into rels + inherit_lemmas(rels); + // switch context to new rels + init(rels); + // re-initialize lemma generalizers + init_lemma_generalizers(); +} + +void context::init(const decl2rel &rels) { + // reset context. Current state is all stored in rels reset(); - it = rels.begin(), end = rels.end(); - for (; it != end; ++it) { - m_rels.insert(it->m_key, it->m_value); - } + // re-initialize context + for (auto &entry : rels) + {m_rels.insert(entry.m_key, entry.m_value);} } unsigned context::get_num_levels(func_decl* p) @@ -2041,12 +2394,13 @@ expr_ref context::get_reachable(func_decl *p) bool context::validate() { - if (!m_params.pdr_validate_result()) { return true; } + if (!m_validate_result) { return true; } std::stringstream msg; switch(m_last_result) { case l_true: { +#if 0 expr_ref cex(m); cex = get_ground_sat_answer(); if (!cex.get()) { @@ -2054,38 +2408,45 @@ bool context::validate() throw default_exception("Cex validation failed\n"); return false; } +#endif + proof_ref cex(m); + cex = get_ground_refutation(); + if (!cex.get()) { + IF_VERBOSE(0, verbose_stream() << "Cex validation failed\n";); + throw default_exception("Cex validation failed\n"); + return false; + } break; } case l_false: { expr_ref_vector refs(m); expr_ref tmp(m); model_ref model; - model_converter_ref mc; vector rs; + model_converter_ref mc; get_level_property(m_inductive_lvl, refs, rs); inductive_property ex(m, mc, rs); ex.to_model(model); - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); var_subst vs(m, false); - for (; it != end; ++it) { - ptr_vector const& rules = it->m_value->rules(); - TRACE ("spacer", tout << "PT: " << it->m_value->head ()->get_name ().str () + for (auto& kv : m_rels) { + ptr_vector const& rules = kv.m_value->rules(); + TRACE ("spacer", tout << "PT: " << kv.m_value->head ()->get_name ().str () << "\n";); - for (unsigned i = 0; i < rules.size(); ++i) { - datalog::rule& r = *rules[i]; + for (auto* rp : rules) { + datalog::rule& r = *rp; TRACE ("spacer", get_datalog_context (). get_rule_manager (). display_smt2(r, tout) << "\n";); - model->eval(r.get_head(), tmp); + tmp = (*model)(r.get_head()); expr_ref_vector fmls(m); fmls.push_back(m.mk_not(tmp)); unsigned utsz = r.get_uninterpreted_tail_size(); unsigned tsz = r.get_tail_size(); for (unsigned j = 0; j < utsz; ++j) { - model->eval(r.get_tail(j), tmp); + tmp = (*model)(r.get_tail(j)); fmls.push_back(tmp); } for (unsigned j = utsz; j < tsz; ++j) { @@ -2104,9 +2465,10 @@ bool context::validate() fv.reverse (); tmp = m.mk_exists(fv.size(), fv.c_ptr(), names.c_ptr(), tmp); } - smt::kernel solver(m, m_pm.fparams2()); - solver.assert_expr(tmp); - lbool res = solver.check(); + ref sol = + mk_smt_solver(m, params_ref::get_empty(), symbol::null); + sol->assert_expr(tmp); + lbool res = sol->check_sat(0, nullptr); if (res != l_false) { msg << "rule validation failed when checking: " << mk_pp(tmp, m); @@ -2133,56 +2495,83 @@ void context::reset_lemma_generalizers() m_lemma_generalizers.reset(); } -void context::init_lemma_generalizers(datalog::rule_set& rules) +// initialize global SMT parameters shared by all solvers +void context::init_global_smt_params() { + m.toggle_proof_mode(PGM_ENABLED); + params_ref p; + if (!m_use_eq_prop) { + p.set_uint("arith.propagation_mode", BP_NONE); + p.set_bool("arith.auto_config_simplex", true); + p.set_bool("arith.propagate_eqs", false); + p.set_bool("arith.eager_eq_axioms", false); + } + p.set_uint("random_seed", m_params.spacer_random_seed()); + + p.set_bool("dump_benchmarks", m_params.spacer_dump_benchmarks()); + p.set_double("dump_threshold", m_params.spacer_dump_threshold()); + + // mbqi + p.set_bool("mbqi", m_params.spacer_mbqi()); + + if (!m_ground_pob) { + p.set_uint("phase_selection", PS_CACHING_CONSERVATIVE2); + p.set_uint("restart_strategy", RS_GEOMETRIC); + p.set_double("restart_factor", 1.5); + p.set_uint("qi.quick_checker", MC_UNSAT); + p.set_double("qi.eager_threshold", 10.0); + p.set_double("qi.lazy_threshold", 20.0); + + // options that we used to set, but are not user visible and + // possibly not very useful + // fparams.m_ng_lift_ite = LI_FULL; + // fparams.m_eliminate_bounds = true; + // fparams.m_pi_use_database = true; + } + + m_pool0->updt_params(p); + m_pool1->updt_params(p); + m_pool2->updt_params(p); +} +void context::init_lemma_generalizers() { reset_lemma_generalizers(); - m.toggle_proof_mode(PGM_ENABLED); - smt_params &fparams = m_pm.fparams (); - if (!m_params.spacer_eq_prop ()) { - fparams.m_arith_bound_prop = BP_NONE; - fparams.m_arith_auto_config_simplex = true; - fparams.m_arith_propagate_eqs = false; - fparams.m_arith_eager_eq_axioms = false; + + if (m_q3_qgen) { + m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, + *this, 0, true)); + m_lemma_generalizers.push_back(alloc(lemma_quantifier_generalizer, *this, + m_params.spacer_q3_qgen_normalize())); } - fparams.m_random_seed = m_params.spacer_random_seed (); - fparams.m_dump_benchmarks = m_params.spacer_vs_dump_benchmarks(); - fparams.m_dump_min_time = m_params.spacer_vs_dump_min_time(); - fparams.m_dump_recheck = m_params.spacer_vs_recheck(); - - fparams.m_mbqi = m_params.spacer_mbqi(); - - if (get_params().spacer_use_eqclass()) { + if (m_use_euf_gen) { m_lemma_generalizers.push_back (alloc(lemma_eq_generalizer, *this)); } // -- AG: commented out because it is causing performance issues at the moment //m_lemma_generalizers.push_back (alloc (unsat_core_generalizer, *this)); - if (m_params.pdr_use_inductive_generalizer()) { + if (m_use_ind_gen) { m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); } - if (m_params.spacer_use_array_eq_generalizer()) { + if (m_use_array_eq_gen) { m_lemma_generalizers.push_back(alloc(lemma_array_eq_generalizer, *this)); } - if (get_params().spacer_lemma_sanity_check()) { + if (m_validate_lemmas) { m_lemma_generalizers.push_back(alloc(lemma_sanity_checker, *this)); } } void context::get_level_property(unsigned lvl, expr_ref_vector& res, - vector& rs) const -{ - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - pred_transformer* r = it->m_value; + vector& rs) const { + for (auto const& kv : m_rels) { + pred_transformer* r = kv.m_value; if (r->head() == m_query_pred) { continue; } - expr_ref conj = r->get_formulas(lvl, false); + expr_ref conj = r->get_formulas(lvl); m_pm.formula_n2o(0, false, conj); res.push_back(conj); ptr_vector sig(r->head()->get_arity(), r->sig()); @@ -2190,12 +2579,9 @@ void context::get_level_property(unsigned lvl, expr_ref_vector& res, } } -void context::simplify_formulas() -{ - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - pred_transformer* r = it->m_value; - r->simplify_formulas(); +void context::simplify_formulas() { + for (auto& kv : m_rels) { + kv.m_value->simplify_formulas(); } } @@ -2203,7 +2589,14 @@ lbool context::solve(unsigned from_lvl) { m_last_result = l_undef; try { - m_last_result = solve_core (from_lvl); + if (m_use_gpdr) { + SASSERT(from_lvl == 0); + m_last_result = gpdr_solve_core(); + } + else { + m_last_result = solve_core (from_lvl); + } + if (m_last_result == l_false) { simplify_formulas(); m_last_result = l_false; @@ -2269,7 +2662,7 @@ unsigned context::get_cex_depth() pred_transformer* pt; // get and discard query rule - fact = m_query->get_last_reach_fact (); + fact = m_query->get_last_rf (); r = &fact->get_rule (); unsigned cex_depth = 0; @@ -2344,7 +2737,7 @@ void context::get_rules_along_trace(datalog::rule_ref_vector& rules) pred_transformer* pt; // get query rule - fact = m_query->get_last_reach_fact (); + fact = m_query->get_last_rf (); r = &fact->get_rule (); rules.push_back (const_cast (r)); TRACE ("spacer", @@ -2430,19 +2823,31 @@ expr_ref context::mk_unsat_answer() const return ex.to_expr(); } + +proof_ref context::get_ground_refutation() { + if (m_last_result != l_true) { + IF_VERBOSE(0, verbose_stream() + << "Sat answer unavailable when result is false\n";); + return proof_ref(m); + } + + ground_sat_answer_op op(*this); + return op(*m_query); +} expr_ref context::get_ground_sat_answer() { if (m_last_result != l_true) { - verbose_stream () << "Sat answer unavailable when result is false\n"; - return expr_ref (m); + IF_VERBOSE(0, verbose_stream() + << "Sat answer unavailable when result is false\n";); + return expr_ref(m); } // treat the following as queues: read from left to right and insert at the right reach_fact_ref_vector reach_facts; ptr_vector preds; ptr_vector pts; - expr_ref_vector cex (m), // pre-order list of ground instances of predicates - cex_facts (m); // equalities for the ground cex using signature constants + expr_ref_vector cex (m); // pre-order list of ground instances of predicates + expr_ref_vector cex_facts (m); // equalities for the ground cex using signature constants // temporary reach_fact *reach_fact; @@ -2451,7 +2856,7 @@ expr_ref context::get_ground_sat_answer() datalog::rule const* r; // get and discard query rule - reach_fact = m_query->get_last_reach_fact (); + reach_fact = m_query->get_last_rf (); r = &reach_fact->get_rule (); // initialize queues @@ -2473,8 +2878,8 @@ expr_ref context::get_ground_sat_answer() { cex.push_back(m.mk_const(preds[0])); } // smt context to obtain local cexes - scoped_ptr cex_ctx = alloc (smt::kernel, m, m_pm.fparams2 ()); - model_evaluator_util mev (m); + ref cex_ctx = + mk_smt_solver(m, params_ref::get_empty(), symbol::null); // preorder traversal of the query derivation tree for (unsigned curr = 0; curr < pts.size (); curr++) { @@ -2493,6 +2898,7 @@ expr_ref context::get_ground_sat_answer() // get child pts preds.reset(); pt->find_predecessors(*r, preds); + for (unsigned j = 0; j < preds.size (); j++) { child_pts.push_back (&(get_pred_transformer (preds[j]))); } @@ -2508,13 +2914,12 @@ expr_ref context::get_ground_sat_answer() SASSERT (child_reach_facts.size () == u_tail_sz); for (unsigned i = 0; i < u_tail_sz; i++) { expr_ref ofml (m); - child_pts.get (i)->get_manager ().formula_n2o - (child_reach_facts[i]->get (), ofml, i); + m_pm.formula_n2o(child_reach_facts[i]->get(), ofml, i); cex_ctx->assert_expr (ofml); } - cex_ctx->assert_expr (pt->transition ()); - cex_ctx->assert_expr (pt->rule2tag (r)); - lbool res = cex_ctx->check (); + cex_ctx->assert_expr(pt->transition()); + cex_ctx->assert_expr(pt->rule2tag(r)); + lbool res = cex_ctx->check_sat(0, nullptr); CTRACE("cex", res == l_false, tout << "Cex fact: " << mk_pp(cex_fact, m) << "\n"; for (unsigned i = 0; i < u_tail_sz; i++) @@ -2527,41 +2932,35 @@ expr_ref context::get_ground_sat_answer() model_ref local_mdl; cex_ctx->get_model (local_mdl); cex_ctx->pop (1); - - model_evaluator_util mev (m); - mev.set_model (*local_mdl); - for (unsigned i = 0; i < child_pts.size (); i++) { - pred_transformer& ch_pt = *(child_pts.get (i)); - unsigned sig_size = ch_pt.sig_size (); - expr_ref_vector ground_fact_conjs (m); - expr_ref_vector ground_arg_vals (m); + local_mdl->set_model_completion(true); + for (unsigned i = 0; i < child_pts.size(); i++) { + pred_transformer& ch_pt = *(child_pts.get(i)); + unsigned sig_size = ch_pt.sig_size(); + expr_ref_vector ground_fact_conjs(m); + expr_ref_vector ground_arg_vals(m); for (unsigned j = 0; j < sig_size; j++) { - expr_ref sig_arg (m), sig_val (m); - sig_arg = m.mk_const (ch_pt.get_manager ().o2o (ch_pt.sig (j), 0, i)); - VERIFY(mev.eval (sig_arg, sig_val, true)); - ground_fact_conjs.push_back (m.mk_eq (sig_arg, sig_val)); - ground_arg_vals.push_back (sig_val); + expr_ref sig_arg(m), sig_val(m); + sig_arg = m.mk_const (m_pm.o2o(ch_pt.sig(j), 0, i)); + sig_val = (*local_mdl)(sig_arg); + ground_fact_conjs.push_back(m.mk_eq(sig_arg, sig_val)); + ground_arg_vals.push_back(sig_val); } if (ground_fact_conjs.size () > 0) { - expr_ref ground_fact (m); - ground_fact = m.mk_and (ground_fact_conjs.size (), ground_fact_conjs.c_ptr ()); - ch_pt.get_manager ().formula_o2n (ground_fact, ground_fact, i); + expr_ref ground_fact(m); + ground_fact = mk_and(ground_fact_conjs); + m_pm.formula_o2n(ground_fact, ground_fact, i); cex_facts.push_back (ground_fact); } else { cex_facts.push_back (m.mk_true ()); } - cex.push_back (m.mk_app (ch_pt.head (), sig_size, ground_arg_vals.c_ptr ())); + cex.push_back(m.mk_app(ch_pt.head(), + sig_size, ground_arg_vals.c_ptr())); } } - TRACE ("spacer", - tout << "ground cex\n"; - for (unsigned i = 0; i < cex.size (); i++) { - tout << mk_pp (cex.get (i), m) << "\n"; - } - ); + TRACE ("spacer", tout << "ground cex\n" << cex << "\n";); - return expr_ref (m.mk_and (cex.size (), cex.c_ptr ()), m); + return expr_ref(m.mk_and(cex.size(), cex.c_ptr()), m); } ///this is where everything starts @@ -2576,17 +2975,28 @@ lbool context::solve_core (unsigned from_lvl) pob *root = m_query->mk_pob(nullptr,from_lvl,0,m.mk_true()); m_pob_queue.set_root (*root); - unsigned max_level = get_params ().spacer_max_level (); + unsigned max_level = m_max_level; - for (unsigned i = 0; i < max_level; ++i) { + for (unsigned i = from_lvl; i < max_level; ++i) { checkpoint(); m_expanded_lvl = infty_level (); m_stats.m_max_query_lvl = lvl; if (check_reachability()) { return l_true; } - if (lvl > 0 && !get_params ().spacer_skip_propagate ()) - if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { return l_false; } + if (lvl > 0 && m_use_propagate) + if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { dump_json(); return l_false; } + + dump_json(); + + if (is_inductive()){ + return l_false; + } + + for (unsigned i = 0; i < m_callbacks.size(); i++){ + if (m_callbacks[i]->unfold()) + m_callbacks[i]->unfold_eh(); + } m_pob_queue.inc_level (); lvl = m_pob_queue.max_level (); @@ -2619,7 +3029,9 @@ bool context::check_reachability () pob_ref last_reachable; - if (get_params().spacer_reset_obligation_queue()) { m_pob_queue.reset(); } + pob_ref_buffer new_pobs; + + if (m_reset_obligation_queue) { m_pob_queue.reset(); } unsigned initial_size = m_stats.m_num_lemmas; unsigned threshold = m_restart_initial_threshold; @@ -2685,40 +3097,45 @@ bool context::check_reachability () } node = m_pob_queue.top (); + m_pob_queue.pop(); + size_t old_sz = m_pob_queue.size(); + (void)old_sz; SASSERT (node->level () <= m_pob_queue.max_level ()); - switch (expand_node(*node)) { + switch (expand_pob(*node, new_pobs)) { case l_true: - SASSERT (m_pob_queue.top () == node.get ()); - m_pob_queue.pop (); + SASSERT(m_pob_queue.size() == old_sz); + SASSERT(new_pobs.empty()); last_reachable = node; last_reachable->close (); - if (m_pob_queue.is_root(*node)) { return true; } + if (m_pob_queue.is_root(*node)) {return true;} break; case l_false: - SASSERT (m_pob_queue.top () == node.get ()); - m_pob_queue.pop (); + SASSERT(m_pob_queue.size() == old_sz); + for (auto pob : new_pobs) { + if (is_requeue(*pob)) {m_pob_queue.push(*pob);} + } - if (node->is_dirty()) { node->clean(); } - - node->inc_level (); - if (get_params ().pdr_flexible_trace () && - (node->level () >= m_pob_queue.max_level () || - m_pob_queue.max_level () - node->level () - <= get_params ().pdr_flexible_trace_depth ())) - { m_pob_queue.push(*node); } - - if (m_pob_queue.is_root(*node)) { return false; } + if (m_pob_queue.is_root(*node)) {return false;} break; case l_undef: - // SASSERT (m_pob_queue.top () != node.get ()); + SASSERT(m_pob_queue.size() == old_sz); + for (auto pob : new_pobs) {m_pob_queue.push(*pob);} break; } + new_pobs.reset(); } UNREACHABLE(); return false; } +/// returns true if the given pob can be re-scheduled +bool context::is_requeue(pob &n) { + if (!m_push_pob) {return false;} + unsigned max_depth = m_push_pob_max_depth; + return (n.level() >= m_pob_queue.max_level() || + m_pob_queue.max_level() - n.level() <= max_depth); +} /// check whether node n is concretely reachable bool context::is_reachable(pob &n) { @@ -2744,7 +3161,7 @@ bool context::is_reachable(pob &n) // used in case n is unreachable unsigned uses_level = infty_level (); - model_ref model; + model_ref mdl; // used in case n is reachable bool is_concrete; @@ -2755,7 +3172,7 @@ bool context::is_reachable(pob &n) unsigned saved = n.level (); n.m_level = infty_level (); - lbool res = n.pt().is_reachable(n, nullptr, &model, + lbool res = n.pt().is_reachable(n, nullptr, &mdl, uses_level, is_concrete, r, reach_pred_used, num_reuse_reach); n.m_level = saved; @@ -2769,12 +3186,10 @@ bool context::is_reachable(pob &n) SASSERT(res == l_true); SASSERT(is_concrete); - model_evaluator_util mev (m); - mev.set_model(*model); // -- update must summary if (r && r->get_uninterpreted_tail_size () > 0) { - reach_fact_ref rf = mk_reach_fact (n, mev, *r); - n.pt ().add_reach_fact (rf.get ()); + reach_fact_ref rf = n.pt().mk_rf (n, *mdl, *r); + n.pt ().add_rf (rf.get ()); } // if n has a derivation, create a new child and report l_undef @@ -2813,23 +3228,47 @@ bool context::is_reachable(pob &n) return next ? is_reachable(*next) : true; } -//this processes a goal and creates sub-goal -lbool context::expand_node(pob& n) +void context::dump_json() { + if (m_params.spacer_print_json().size()) { + std::ofstream of; + of.open(m_params.spacer_print_json().bare_str()); + m_json_marshaller.marshal(of); + of.close(); + } +} + +void context::predecessor_eh() +{ + for (unsigned i = 0; i < m_callbacks.size(); i++) { + if (m_callbacks[i]->predecessor()) + m_callbacks[i]->predecessor_eh(); + } +} + +/// Checks whether the given pob is reachable +/// returns l_true if reachable, l_false if unreachable +/// returns l_undef if reachability cannot be decided +/// out contains new pobs to add to the queue in case the result is l_undef +lbool context::expand_pob(pob& n, pob_ref_buffer &out) +{ + SASSERT(out.empty()); + pob::on_expand_event _evt(n); TRACE ("spacer", - tout << "expand-node: " << n.pt().head()->get_name() + tout << "expand-pob: " << n.pt().head()->get_name() << " level: " << n.level() - << " depth: " << (n.depth () - m_pob_queue.min_depth ()) << "\n" + << " depth: " << (n.depth () - m_pob_queue.min_depth ()) + << " fvsz: " << n.get_free_vars_size() << "\n" << mk_pp(n.post(), m) << "\n";); STRACE ("spacer.expand-add", - tout << "expand-node: " << n.pt().head()->get_name() + tout << "** expand-pob: " << n.pt().head()->get_name() << " level: " << n.level() << " depth: " << (n.depth () - m_pob_queue.min_depth ()) << "\n" << mk_epp(n.post(), m) << "\n\n";); TRACE ("core_array_eq", - tout << "expand-node: " << n.pt().head()->get_name() + tout << "expand-pob: " << n.pt().head()->get_name() << " level: " << n.level() << " depth: " << (n.depth () - m_pob_queue.min_depth ()) << "\n" << mk_pp(n.post(), m) << "\n";); @@ -2857,23 +3296,26 @@ lbool context::expand_node(pob& n) unsigned num_reuse_reach = 0; - if (get_params().pdr_flexible_trace() && n.pt().is_blocked(n, uses_level)) { + if (m_push_pob && n.pt().is_blocked(n, uses_level)) { // if (!m_pob_queue.is_root (n)) n.close (); IF_VERBOSE (1, verbose_stream () << " K " << std::fixed << std::setprecision(2) << watch.get_seconds () << "\n";); - + n.inc_level(); + out.push_back(&n); return l_false; } - smt_params &fparams = m_pm.fparams(); - flet _arith_ignore_int_(fparams.m_arith_ignore_int, - m_weak_abs && n.weakness() < 1); - flet _array_weak_(fparams.m_array_weak, - m_weak_abs && n.weakness() < 2); + if (/* XXX noop */ n.pt().is_qblocked(n)) { + STRACE("spacer.expand-add", + tout << "This pob can be blocked by instantiation\n";); + } + + predecessor_eh(); lbool res = n.pt ().is_reachable (n, &cube, &model, uses_level, is_concrete, r, reach_pred_used, num_reuse_reach); + if (model) model->set_model_completion(false); checkpoint (); IF_VERBOSE (1, verbose_stream () << "." << std::flush;); switch (res) { @@ -2882,15 +3324,13 @@ lbool context::expand_node(pob& n) // update stats m_stats.m_num_reuse_reach += num_reuse_reach; - model_evaluator_util mev (m); - mev.set_model (*model); // must-reachable if (is_concrete) { // -- update must summary if (r && r->get_uninterpreted_tail_size() > 0) { - reach_fact_ref rf = mk_reach_fact (n, mev, *r); + reach_fact_ref rf = n.pt().mk_rf (n, *model, *r); checkpoint (); - n.pt ().add_reach_fact (rf.get ()); + n.pt ().add_rf (rf.get ()); checkpoint (); } @@ -2911,10 +3351,8 @@ lbool context::expand_node(pob& n) // move derivation over to the next obligation next->set_derivation (deriv.detach()); - // remove the current node from the queue if it is at the top - if (m_pob_queue.top() == &n) { m_pob_queue.pop(); } - - m_pob_queue.push (*next); + // this is the new node to add + out.push_back (next); } } @@ -2926,17 +3364,20 @@ lbool context::expand_node(pob& n) } // create a child of n - VERIFY(create_children (n, *r, mev, reach_pred_used)); + + out.push_back(&n); + VERIFY(create_children (n, *r, *model, reach_pred_used, out)); IF_VERBOSE(1, verbose_stream () << " U " << std::fixed << std::setprecision(2) << watch.get_seconds () << "\n";); return l_undef; } + case l_false: // n is unreachable, create new summary facts - case l_false: { + { timeit _timer (is_trace_enabled("spacer_timeit"), - "spacer::expand_node::false", + "spacer::expand_pob::false", verbose_stream ()); // -- only update expanded level when new lemmas are generated at it. @@ -2959,6 +3400,11 @@ lbool context::expand_node(pob& n) checkpoint (); (*m_lemma_generalizers[i])(lemma); } + DEBUG_CODE( + lemma_sanity_checker sanity_checker(*this); + sanity_checker(lemma); + ); + TRACE("spacer", tout << "invariant state: " << (is_infty_level(lemma->level())?"(inductive)":"") @@ -2968,10 +3414,17 @@ lbool context::expand_node(pob& n) if (v) { m_stats.m_num_lemmas++; } // Optionally update the node to be the negation of the lemma - if (v && get_params().spacer_use_lemma_as_cti()) { + if (v && m_use_lemma_as_pob) { n.new_post (mk_and(lemma->get_cube())); n.set_farkas_generalizer (false); + // XXX hack while refactoring is in progress + n.clean(); } + + // schedule the node to be placed back in the queue + n.inc_level(); + out.push_back(&n); + CASSERT("spacer", n.level() == 0 || check_invariant(n.level()-1)); @@ -2986,27 +3439,29 @@ lbool context::expand_node(pob& n) if (n.weakness() < 100 /* MAX_WEAKENSS */) { bool has_new_child = false; SASSERT(m_weak_abs); - m_stats.m_expand_node_undef++; + m_stats.m_expand_pob_undef++; if (r && r->get_uninterpreted_tail_size() > 0) { - model_evaluator_util mev(m); - mev.set_model(*model); // do not trust reach_pred_used for (unsigned i = 0, sz = reach_pred_used.size(); i < sz; ++i) { reach_pred_used[i] = false; } - has_new_child = create_children(n,*r,mev,reach_pred_used); + has_new_child = create_children(n, *r, *model, reach_pred_used, out); } IF_VERBOSE(1, verbose_stream() << " UNDEF " << std::fixed << std::setprecision(2) << watch.get_seconds () << "\n";); - if (has_new_child) { return l_undef; } + if (has_new_child) { + // ensure that n is placed back in the queue + out.push_back(&n); + return l_undef; + } // -- failed to create a child, bump weakness and repeat // -- the recursion is bounded by the levels of weakness supported + SASSERT(out.empty()); n.bump_weakness(); - return expand_node(n); + return expand_pob(n, out); } - TRACE("spacer", tout << "unknown state: " - << mk_pp(m_pm.mk_and(cube), m) << "\n";); + TRACE("spacer", tout << "unknown state: " << mk_and(cube) << "\n";); throw unknown_exception(); } UNREACHABLE(); @@ -3033,9 +3488,11 @@ bool context::propagate(unsigned min_prop_lvl, if (full_prop_lvl < max_prop_lvl) { full_prop_lvl = max_prop_lvl; } - if (m_params.pdr_simplify_formulas_pre()) { + if (m_simplify_formulas_pre) { simplify_formulas(); } + STRACE ("spacer.expand-add", tout << "Propagating\n";); + IF_VERBOSE (1, verbose_stream () << "Propagating: " << std::flush;); for (unsigned lvl = min_prop_lvl; lvl <= full_prop_lvl; lvl++) { @@ -3049,18 +3506,17 @@ bool context::propagate(unsigned min_prop_lvl, tout << "In full propagation\n";); bool all_propagated = true; - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { + for (auto & kv : m_rels) { checkpoint(); - pred_transformer& r = *it->m_value; + pred_transformer& r = *kv.m_value; all_propagated = r.propagate_to_next_level(lvl) && all_propagated; } //CASSERT("spacer", check_invariant(lvl)); if (all_propagated) { - for (it = m_rels.begin(); it != end; ++it) { + for (auto& kv : m_rels) { checkpoint (); - pred_transformer& r = *it->m_value; + pred_transformer& r = *kv.m_value; r.propagate_to_infinity (lvl); } if (lvl <= max_prop_lvl) { @@ -3076,7 +3532,7 @@ bool context::propagate(unsigned min_prop_lvl, return true; } else if (all_propagated && lvl > max_prop_lvl) { break; } } - if (m_params.pdr_simplify_formulas_post()) { + if (m_simplify_formulas_post) { simplify_formulas(); } @@ -3084,54 +3540,52 @@ bool context::propagate(unsigned min_prop_lvl, return false; } -reach_fact *context::mk_reach_fact (pob& n, model_evaluator_util &mev, - const datalog::rule& r) +reach_fact *pred_transformer::mk_rf(pob& n, model &mdl, const datalog::rule& r) { + SASSERT(&n.pt() == this); timeit _timer1 (is_trace_enabled("spacer_timeit"), - "mk_reach_fact", + "mk_rf", verbose_stream ()); expr_ref res(m); reach_fact_ref_vector child_reach_facts; - pred_transformer& pt = n.pt (); - ptr_vector preds; - pt.find_predecessors (r, preds); + find_predecessors (r, preds); expr_ref_vector path_cons (m); - path_cons.push_back (pt.get_transition (r)); + path_cons.push_back (get_transition (r)); app_ref_vector vars (m); for (unsigned i = 0; i < preds.size (); i++) { func_decl* pred = preds[i]; - pred_transformer& ch_pt = get_pred_transformer (pred); + pred_transformer& ch_pt = ctx.get_pred_transformer (pred); // get a reach fact of body preds used in the model expr_ref o_ch_reach (m); - reach_fact *kid = ch_pt.get_used_origin_reach_fact (mev, i); + reach_fact *kid = ch_pt.get_used_origin_rf(mdl, i); child_reach_facts.push_back (kid); - m_pm.formula_n2o (kid->get (), o_ch_reach, i); + pm.formula_n2o (kid->get (), o_ch_reach, i); path_cons.push_back (o_ch_reach); // collect o-vars to eliminate for (unsigned j = 0; j < pred->get_arity (); j++) - { vars.push_back(m.mk_const(m_pm.o2o(ch_pt.sig(j), 0, i))); } + { vars.push_back(m.mk_const(pm.o2o(ch_pt.sig(j), 0, i))); } const ptr_vector &v = kid->aux_vars (); for (unsigned j = 0, sz = v.size (); j < sz; ++j) - { vars.push_back(m.mk_const(m_pm.n2o(v [j]->get_decl(), i))); } + { vars.push_back(m.mk_const(pm.n2o(v [j]->get_decl(), i))); } } // collect aux vars to eliminate - ptr_vector& aux_vars = pt.get_aux_vars (r); - bool elim_aux = get_params ().spacer_elim_aux (); + ptr_vector& aux_vars = get_aux_vars (r); + bool elim_aux = ctx.elim_aux(); if (elim_aux) { vars.append(aux_vars.size(), aux_vars.c_ptr()); } - res = m_pm.mk_and (path_cons); + res = mk_and (path_cons); // -- pick an implicant from the path condition - if (get_params().spacer_reach_dnf()) { + if (ctx.reach_dnf()) { expr_ref_vector u(m), lits(m); u.push_back (res); - compute_implicant_literals (mev, u, lits); - res = m_pm.mk_and (lits); + compute_implicant_literals (mdl, u, lits); + res = mk_and (lits); } @@ -3146,9 +3600,9 @@ reach_fact *context::mk_reach_fact (pob& n, model_evaluator_util &mev, { timeit _timer1 (is_trace_enabled("spacer_timeit"), - "mk_reach_fact::qe_project", + "mk_rf::qe_project", verbose_stream ()); - qe_project (m, vars, res, mev.get_model (), false, m_use_native_mbp); + mbp(vars, res, mdl, false, true /* force or skolemize */); } @@ -3176,102 +3630,94 @@ reach_fact *context::mk_reach_fact (pob& n, model_evaluator_util &mev, \brief create children states from model cube. */ bool context::create_children(pob& n, datalog::rule const& r, - model_evaluator_util &mev, - const vector &reach_pred_used) + model &mdl, + const vector &reach_pred_used, + pob_ref_buffer &out) { - scoped_watch _w_ (m_create_children_watch); pred_transformer& pt = n.pt(); - expr* const T = pt.get_transition(r); - expr* const phi = n.post(); TRACE("spacer", tout << "Model:\n"; - model_smt2_pp(tout, m, *mev.get_model (), 0); + model_smt2_pp(tout, m, mdl, 0); tout << "\n"; - tout << "Transition:\n" << mk_pp(T, m) << "\n"; - tout << "Phi:\n" << mk_pp(phi, m) << "\n";); + tout << "Transition:\n" << mk_pp(pt.get_transition(r), m) << "\n"; + tout << "Pob:\n" << mk_pp(n.post(), m) << "\n";); SASSERT (r.get_uninterpreted_tail_size () > 0); ptr_vector preds; pt.find_predecessors(r, preds); - ptr_vector pred_pts; - - for (ptr_vector::iterator it = preds.begin (); - it != preds.end (); it++) { - pred_pts.push_back (&get_pred_transformer (*it)); - } - - expr_ref_vector forms(m), Phi(m); // obtain all formulas to consider for model generalization - forms.push_back(T); - forms.push_back(phi); + expr_ref_vector forms(m), lits(m); + forms.push_back(pt.get_transition(r)); + forms.push_back(n.post()); - compute_implicant_literals (mev, forms, Phi); - - //pt.remove_predecessors (Phi); + compute_implicant_literals (mdl, forms, lits); + expr_ref phi = mk_and (lits); + // primed variables of the head app_ref_vector vars(m); - unsigned sig_size = pt.head()->get_arity(); - for (unsigned i = 0; i < sig_size; ++i) { + for (unsigned i = 0, sz = pt.head()->get_arity(); i < sz; ++i) { vars.push_back(m.mk_const(m_pm.o2n(pt.sig(i), 0))); } + // local variables of the rule ptr_vector& aux_vars = pt.get_aux_vars(r); vars.append(aux_vars.size(), aux_vars.c_ptr()); + // skolems of the pob n.get_skolems(vars); - expr_ref phi1 = m_pm.mk_and (Phi); - qe_project (m, vars, phi1, mev.get_model (), true, - m_use_native_mbp, !m_ground_cti); + n.pt().mbp(vars, phi, mdl, true, use_ground_pob()); //qe::reduce_array_selects (*mev.get_model (), phi1); - SASSERT (!m_ground_cti || vars.empty ()); + SASSERT (!m_ground_pob || vars.empty ()); TRACE ("spacer", - tout << "Implicant\n"; - tout << mk_pp (m_pm.mk_and (Phi), m) << "\n"; - tout << "Projected Implicant\n" << mk_pp (phi1, m) << "\n"; + tout << "Implicant:\n"; + tout << lits << "\n"; + tout << "After MBP:\n" << phi << "\n"; + if (!vars.empty()) + tout << "Failed to eliminate: " << vars << "\n"; ); - // expand literals. Ideally, we do not want to split aliasing - // equalities. Unfortunately, the interface does not allow for - // that yet. - // XXX This mixes up with derivation. Needs more thought. - // Phi.reset (); - // flatten_and (phi1, Phi); - // if (!Phi.empty ()) - // { - // expand_literals (m, Phi); - // phi1 = m_pm.mk_and (Phi); - // } + if (m_use_gpdr && preds.size() > 1) { + SASSERT(vars.empty()); + return gpdr_create_split_children(n, r, phi, mdl, out); + } + derivation *deriv = alloc(derivation, n, r, phi, vars); + + // pick an order to process children + unsigned_vector kid_order; + kid_order.resize(preds.size(), 0); + for (unsigned i = 0, sz = preds.size(); i < sz; ++i) kid_order[i] = i; + if (m_children_order == CO_REV_RULE) { + kid_order.reverse(); + } + else if (m_children_order == CO_RANDOM) { + shuffle(kid_order.size(), kid_order.c_ptr(), m_random); + } - derivation *deriv = alloc (derivation, n, r, phi1, vars); for (unsigned i = 0, sz = preds.size(); i < sz; ++i) { - unsigned j; - if (get_params ().spacer_order_children () == 1) - // -- reverse order - { j = sz - i - 1; } - else - // -- default order - { j = i; } + unsigned j = kid_order[i]; - pred_transformer &pt = get_pred_transformer (preds [j]); + pred_transformer &pt = get_pred_transformer(preds.get(j)); const ptr_vector *aux = nullptr; expr_ref sum(m); - // XXX This is a bit confusing. The summary is returned over - // XXX o-variables. But it is simpler if it is returned over n-variables instead. - sum = pt.get_origin_summary (mev, prev_level (n.level ()), - j, reach_pred_used [j], &aux); - deriv->add_premise (pt, j, sum, reach_pred_used [j], aux); + sum = pt.get_origin_summary (mdl, prev_level(n.level()), + j, reach_pred_used[j], &aux); + if (!sum) { + dealloc(deriv); + return false; + } + deriv->add_premise (pt, j, sum, reach_pred_used[j], aux); } // create post for the first child and add to queue - pob* kid = deriv->create_first_child (mev); + pob* kid = deriv->create_first_child (mdl); // -- failed to create derivation, cleanup and bail out if (!kid) { @@ -3282,16 +3728,17 @@ bool context::create_children(pob& n, datalog::rule const& r, kid->set_derivation (deriv); // Optionally disable derivation optimization - if (!get_params().spacer_use_derivations()) { kid->reset_derivation(); } + if (!m_use_derivations) { kid->reset_derivation(); } // -- deriviation is abstract if the current weak model does // -- not satisfy 'T && phi'. It is possible to recover from // -- that more gracefully. For now, we just remove the // -- derivation completely forcing it to be recomputed - if (m_weak_abs && (!mev.is_true(T) || !mev.is_true(phi))) + if (m_weak_abs && (!mdl.is_true(pt.get_transition(r)) || + !mdl.is_true(n.post()))) { kid->reset_derivation(); } - m_pob_queue.push (*kid); + out.push_back(kid); m_stats.m_num_queries++; return true; } @@ -3301,52 +3748,66 @@ bool context::create_children(pob& n, datalog::rule const& r, void context::collect_statistics(statistics& st) const { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (it = m_rels.begin(); it != end; ++it) { - it->m_value->collect_statistics(st); + m_pool0->collect_statistics(st); + m_pool1->collect_statistics(st); + m_pool2->collect_statistics(st); + + for (auto const& kv : m_rels) { + kv.m_value->collect_statistics(st); } + + // -- number of times a pob for some predicate transformer has + // -- been created st.update("SPACER num queries", m_stats.m_num_queries); - st.update("SPACER num reach queries", m_stats.m_num_reach_queries); + // -- number of times a reach fact was true in some model st.update("SPACER num reuse reach facts", m_stats.m_num_reuse_reach); + // -- maximum level at which any query was asked st.update("SPACER max query lvl", m_stats.m_max_query_lvl); + // -- maximum depth st.update("SPACER max depth", m_stats.m_max_depth); + // -- level at which safe inductive invariant was found st.update("SPACER inductive level", m_inductive_lvl); + // -- length of the counterexample st.update("SPACER cex depth", m_stats.m_cex_depth); - st.update("SPACER expand node undef", m_stats.m_expand_node_undef); + // -- number of times expand_pobresulted in undef + st.update("SPACER expand pob undef", m_stats.m_expand_pob_undef); + // -- number of distinct lemmas constructed st.update("SPACER num lemmas", m_stats.m_num_lemmas); + // -- number of restarts taken st.update("SPACER restarts", m_stats.m_num_restarts); + // -- time to initialize the rules st.update ("time.spacer.init_rules", m_init_rules_watch.get_seconds ()); + // -- time in the main solve loop st.update ("time.spacer.solve", m_solve_watch.get_seconds ()); + // -- time in lemma propagation (i.e., pushing) st.update ("time.spacer.solve.propagate", m_propagate_watch.get_seconds ()); + // -- time in reachability (i.e., blocking) st.update ("time.spacer.solve.reach", m_reach_watch.get_seconds ()); + // -- time in deciding whether a pob is must-reachable st.update ("time.spacer.solve.reach.is-reach", m_is_reach_watch.get_seconds ()); + // -- time in creating new predecessors st.update ("time.spacer.solve.reach.children", m_create_children_watch.get_seconds ()); - m_pm.collect_statistics(st); + st.update("spacer.random_seed", m_params.spacer_random_seed()); + st.update("spacer.lemmas_imported", m_stats.m_num_lemmas_imported); + st.update("spacer.lemmas_discarded", m_stats.m_num_lemmas_discarded); for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) { m_lemma_generalizers[i]->collect_statistics(st); } - - // brunch out - verbose_stream () << "BRUNCH_STAT max_query_lvl " << m_stats.m_max_query_lvl << "\n"; - verbose_stream () << "BRUNCH_STAT num_queries " << m_stats.m_num_queries << "\n"; - verbose_stream () << "BRUNCH_STAT num_reach_queries " << m_stats.m_num_reach_queries << "\n"; - verbose_stream () << "BRUNCH_STAT num_reach_reuse " << m_stats.m_num_reuse_reach << "\n"; - verbose_stream () << "BRUNCH_STAT inductive_lvl " << m_inductive_lvl << "\n"; - verbose_stream () << "BRUNCH_STAT max_depth " << m_stats.m_max_depth << "\n"; - verbose_stream () << "BRUNCH_STAT cex_depth " << m_stats.m_cex_depth << "\n"; } void context::reset_statistics() { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (it = m_rels.begin(); it != end; ++it) { - it->m_value->reset_statistics(); + m_pool0->reset_statistics(); + m_pool1->reset_statistics(); + m_pool2->reset_statistics(); + + for (auto & kv : m_rels) { + kv.m_value->reset_statistics(); } m_stats.reset(); - m_pm.reset_statistics(); for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) { m_lemma_generalizers[i]->reset_statistics(); @@ -3362,10 +3823,9 @@ void context::reset_statistics() bool context::check_invariant(unsigned lvl) { - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { + for (auto &entry : m_rels) { checkpoint(); - if (!check_invariant(lvl, it->m_key)) { + if (!check_invariant(lvl, entry.m_key)) { return false; } } @@ -3374,17 +3834,18 @@ bool context::check_invariant(unsigned lvl) bool context::check_invariant(unsigned lvl, func_decl* fn) { - smt::kernel ctx(m, m_pm.fparams2()); + ref ctx = mk_smt_solver(m, params_ref::get_empty(), symbol::null); pred_transformer& pt = *m_rels.find(fn); expr_ref_vector conj(m); - expr_ref inv = pt.get_formulas(next_level(lvl), false); + expr_ref inv = pt.get_formulas(next_level(lvl)); if (m.is_true(inv)) { return true; } pt.add_premises(m_rels, lvl, conj); conj.push_back(m.mk_not(inv)); expr_ref fml(m.mk_and(conj.size(), conj.c_ptr()), m); - ctx.assert_expr(fml); - lbool result = ctx.check(); - TRACE("spacer", tout << "Check invariant level: " << lvl << " " << result << "\n" << mk_pp(fml, m) << "\n";); + ctx->assert_expr(fml); + lbool result = ctx->check_sat(0, nullptr); + TRACE("spacer", tout << "Check invariant level: " << lvl << " " << result + << "\n" << mk_pp(fml, m) << "\n";); return result == l_false; } @@ -3393,10 +3854,9 @@ expr_ref context::get_constraints (unsigned level) expr_ref res(m); expr_ref_vector constraints(m); - decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); - for (; it != end; ++it) { - pred_transformer& r = *it->m_value; - expr_ref c = r.get_formulas(level, false); + for (auto & kv : m_rels) { + pred_transformer& r = *kv.m_value; + expr_ref c = r.get_formulas(level); if (m.is_true(c)) { continue; } @@ -3412,30 +3872,68 @@ expr_ref context::get_constraints (unsigned level) } if (constraints.empty()) { return expr_ref(m.mk_true(), m); } - return m_pm.mk_and (constraints); + return mk_and (constraints); } -void context::add_constraints (unsigned level, const expr_ref& c) +void context::add_constraint (expr *c, unsigned level) { - if (!c.get()) { return; } + if (!c) { return; } if (m.is_true(c)) { return; } - expr_ref_vector constraints (m); - constraints.push_back (c); - flatten_and (constraints); - - for (unsigned i = 0, sz = constraints.size(); i < sz; ++i) { - expr *c = constraints.get (i); expr *e1, *e2; if (m.is_implies(c, e1, e2)) { SASSERT (is_app (e1)); pred_transformer *r = nullptr; - if (m_rels.find (to_app (e1)->get_decl (), r)) - { r->add_lemma(e2, level); } + if (m_rels.find (to_app (e1)->get_decl (), r)){ + lemma_ref lem = alloc(lemma, m, e2, level); + lem.get()->set_external(true); + if (r->add_lemma(lem.get())) { + this->m_stats.m_num_lemmas_imported++; + } + else{ + this->m_stats.m_num_lemmas_discarded++; + } } } } +void context::new_lemma_eh(pred_transformer &pt, lemma *lem) { + if (m_params.spacer_print_json().size()) + m_json_marshaller.register_lemma(lem); + bool handle=false; + for (unsigned i = 0; i < m_callbacks.size(); i++) { + handle|=m_callbacks[i]->new_lemma(); + } + if (!handle) + return; + if ((is_infty_level(lem->level()) && m_params.spacer_p3_share_invariants()) || + (!is_infty_level(lem->level()) && m_params.spacer_p3_share_lemmas())) { + expr_ref_vector args(m); + for (unsigned i = 0; i < pt.sig_size(); ++i) { + args.push_back(m.mk_const(pt.get_manager().o2n(pt.sig(i), 0))); + } + expr *app = m.mk_app(pt.head(), pt.sig_size(), args.c_ptr()); + expr *lemma = m.mk_implies(app, lem->get_expr()); + for (unsigned i = 0; i < m_callbacks.size(); i++) { + if (m_callbacks[i]->new_lemma()) + m_callbacks[i]->new_lemma_eh(lemma, lem->level()); + } + } +} + +void context::new_pob_eh(pob *p) { + if (m_params.spacer_print_json().size()) + m_json_marshaller.register_pob(p); +} + +bool context::is_inductive() { + // check that inductive level (F infinity) of the query predicate + // contains a constant false + + return false; +} + +/// pob_lt operator inline bool pob_lt::operator() (const pob *pn1, const pob *pn2) const { SASSERT (pn1); @@ -3492,7 +3990,7 @@ inline bool pob_lt::operator() (const pob *pn1, const pob *pn2) const /** XXX Identical nodes. This should not happen. However, * currently, when propagating reachability, we might call - * expand_node() twice on the same node, causing it to generate + * expand_pob() twice on the same node, causing it to generate * the same proof obligation multiple times */ return &n1 < &n2; } diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index a95b1bdb9..37b035b98 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -28,11 +28,12 @@ Notes: #undef max #endif #include - +#include "util/scoped_ptr_vector.h" #include "muz/spacer/spacer_manager.h" #include "muz/spacer/spacer_prop_solver.h" +#include "muz/spacer/spacer_json.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { class rule_set; @@ -41,6 +42,8 @@ namespace datalog { namespace spacer { +class model_search; + class pred_transformer; class derivation; class pob_queue; @@ -52,6 +55,7 @@ typedef obj_map decl2rel; class pob; typedef ref pob_ref; typedef sref_vector pob_ref_vector; +typedef sref_buffer pob_ref_buffer; class reach_fact; typedef ref reach_fact_ref; @@ -66,6 +70,9 @@ class reach_fact { const datalog::rule &m_rule; reach_fact_ref_vector m_justification; + // variable used to tag this reach fact in an incremental disjunction + app_ref m_tag; + bool m_init; public: @@ -73,10 +80,10 @@ public: expr* fact, const ptr_vector &aux_vars, bool init = false) : m_ref_count (0), m_fact (fact, m), m_aux_vars (aux_vars), - m_rule(rule), m_init (init) {} + m_rule(rule), m_tag(m), m_init (init) {} reach_fact (ast_manager &m, const datalog::rule &rule, expr* fact, bool init = false) : - m_ref_count (0), m_fact (fact, m), m_rule(rule), m_init (init) {} + m_ref_count (0), m_fact (fact, m), m_rule(rule), m_tag(m), m_init (init) {} bool is_init () {return m_init;} const datalog::rule& get_rule () {return m_rule;} @@ -87,6 +94,9 @@ public: expr *get () {return m_fact.get ();} const ptr_vector &aux_vars () {return m_aux_vars;} + app* tag() const {SASSERT(m_tag); return m_tag;} + void set_tag(app* tag) {m_tag = tag;} + void inc_ref () {++m_ref_count;} void dec_ref () { @@ -110,10 +120,14 @@ class lemma { ast_manager &m; expr_ref m_body; expr_ref_vector m_cube; + app_ref_vector m_zks; app_ref_vector m_bindings; - unsigned m_lvl; + unsigned m_lvl; // current level of the lemma + unsigned m_init_lvl; // level at which lemma was created pob_ref m_pob; - bool m_new_pob; + model_ref m_ctp; // counter-example to pushing + bool m_external; + unsigned m_bumped; void mk_expr_core(); void mk_cube_core(); @@ -124,6 +138,15 @@ public: // lemma(const lemma &other) = delete; ast_manager &get_ast_manager() {return m;} + + model_ref& get_ctp() {return m_ctp;} + bool has_ctp() {return !is_inductive() && m_ctp;} + void set_ctp(model_ref &v) {m_ctp = v;} + void reset_ctp() {m_ctp.reset();} + + void bump() {m_bumped++;} + unsigned get_bumped() {return m_bumped;} + expr *get_expr(); bool is_false(); expr_ref_vector const &get_cube(); @@ -131,21 +154,30 @@ public: bool has_pob() {return m_pob;} pob_ref &get_pob() {return m_pob;} + inline unsigned weakness(); + void add_skolem(app *zk, app* b); + + inline void set_external(bool ext){m_external = ext;} + inline bool external() { return m_external;} + + bool is_inductive() const {return is_infty_level(m_lvl);} unsigned level () const {return m_lvl;} - void set_level (unsigned lvl) {m_lvl = lvl;} + unsigned init_level() const {return m_init_lvl;} + void set_level (unsigned lvl); app_ref_vector& get_bindings() {return m_bindings;} - void add_binding(app_ref_vector const &binding) {m_bindings.append(binding);} + bool has_binding(app_ref_vector const &binding); + void add_binding(app_ref_vector const &binding); + void instantiate(expr * const * exprs, expr_ref &result, expr *e = nullptr); void mk_insts(expr_ref_vector& inst, expr* e = nullptr); bool is_ground () {return !is_quantifier (get_expr());} void inc_ref () {++m_ref_count;} - void dec_ref () - { - SASSERT (m_ref_count > 0); - --m_ref_count; - if(m_ref_count == 0) { dealloc(this); } - } + void dec_ref () { + SASSERT (m_ref_count > 0); + --m_ref_count; + if (m_ref_count == 0) {dealloc(this);} + } }; struct lemma_lt_proc : public std::binary_function { @@ -167,8 +199,13 @@ struct lemma_lt_proc : public std::binary_function { class pred_transformer { struct stats { - unsigned m_num_propagations; - unsigned m_num_invariants; + unsigned m_num_propagations; // num of times lemma is pushed higher + unsigned m_num_invariants; // num of infty lemmas found + unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing + unsigned m_num_is_invariant; // num of times lemmas are pushed + unsigned m_num_lemma_level_jump; // lemma learned at higher level than expected + unsigned m_num_reach_queries; + stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; @@ -177,12 +214,13 @@ class pred_transformer { #include "muz/spacer/spacer_legacy_frames.h" class frames { private: - pred_transformer &m_pt; - lemma_ref_vector m_lemmas; - unsigned m_size; + pred_transformer &m_pt; // parent pred_transformer + lemma_ref_vector m_pinned_lemmas; // all created lemmas + lemma_ref_vector m_lemmas; // active lemmas + unsigned m_size; // num of frames - bool m_sorted; - lemma_lt_proc m_lt; + bool m_sorted; // true if m_lemmas is sorted by m_lt + lemma_lt_proc m_lt; // sort order for m_lemmas void sort (); @@ -191,42 +229,42 @@ class pred_transformer { ~frames() {} void simplify_formulas (); - pred_transformer& pt () {return m_pt;} + pred_transformer& pt() const {return m_pt;} + const lemma_ref_vector &lemmas() const {return m_lemmas;} - void get_frame_lemmas (unsigned level, expr_ref_vector &out) { - for (unsigned i = 0, sz = m_lemmas.size (); i < sz; ++i) - if(m_lemmas[i]->level() == level) { - out.push_back(m_lemmas[i]->get_expr()); + void get_frame_lemmas (unsigned level, expr_ref_vector &out) const { + for (auto &lemma : m_lemmas) { + if (lemma->level() == level) { + out.push_back(lemma->get_expr()); } + } } - void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out) { - for (unsigned i = 0, sz = m_lemmas.size (); i < sz; ++i) - if(m_lemmas [i]->level() >= level) { - out.push_back(m_lemmas[i]->get_expr()); + void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out) const { + for (auto &lemma : m_lemmas) { + if (lemma->level() >= level) { + out.push_back(lemma->get_expr()); } + } } - unsigned size () const {return m_size;} unsigned lemma_size () const {return m_lemmas.size ();} void add_frame () {m_size++;} void inherit_frames (frames &other) { - for (unsigned i = 0, sz = other.m_lemmas.size (); i < sz; ++i) { - lemma_ref lem = alloc(lemma, m_pt.get_ast_manager(), - other.m_lemmas[i]->get_expr (), - other.m_lemmas[i]->level()); - lem->add_binding(other.m_lemmas[i]->get_bindings()); - add_lemma(lem.get()); + for (auto &other_lemma : other.m_lemmas) { + lemma_ref new_lemma = alloc(lemma, m_pt.get_ast_manager(), + other_lemma->get_expr(), + other_lemma->level()); + new_lemma->add_binding(other_lemma->get_bindings()); + add_lemma(new_lemma.get()); } m_sorted = false; } - bool add_lemma (lemma *lem); + bool add_lemma (lemma *new_lemma); void propagate_to_infinity (unsigned level); bool propagate_to_next_level (unsigned level); - - }; /** @@ -250,122 +288,179 @@ class pred_transformer { app_ref_vector b(m_pt.get_ast_manager()); return mk_pob (parent, level, depth, post, b); } + unsigned size() const {return m_pinned.size();} }; - typedef obj_map rule2expr; - typedef obj_map > rule2apps; + class pt_rule { + const datalog::rule &m_rule; + expr_ref m_trans; // ground version of m_rule + ptr_vector m_auxs; // auxiliary variables in m_trans + app_ref_vector m_reps; // map from fv in m_rule to ground constants + app_ref m_tag; // a unique tag for the rule - manager& pm; // spacer-manager - ast_manager& m; // manager - context& ctx; + public: + pt_rule(ast_manager &m, const datalog::rule &r) : + m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} - func_decl_ref m_head; // predicate - func_decl_ref_vector m_sig; // signature - ptr_vector m_use; // places where 'this' is referenced. - ptr_vector m_rules; // rules used to derive transformer - prop_solver m_solver; // solver context - solver* m_reach_ctx; // context for reachability facts - pobs m_pobs; - frames m_frames; - reach_fact_ref_vector m_reach_facts; // reach facts - /// Number of initial reachability facts - unsigned m_rf_init_sz; - obj_map m_tag2rule; // map tag predicate to rule. - rule2expr m_rule2tag; // map rule to predicate tag. - rule2inst m_rule2inst; // map rules to instantiations of indices - rule2expr m_rule2transition; // map rules to transition - rule2apps m_rule2vars; // map rule to auxiliary variables - expr_ref m_transition; // transition relation. - expr_ref m_initial_state; // initial state. - app_ref m_extend_lit; // literal to extend initial state + const datalog::rule &rule() const {return m_rule;} + + void set_tag(expr *tag) {SASSERT(is_app(tag)); set_tag(to_app(tag));} + void set_tag(app* tag) {m_tag = tag;} + app* tag() const {return m_tag;} + ptr_vector &auxs() {return m_auxs;} + void set_auxs(ptr_vector &v) {m_auxs.reset(); m_auxs.append(v);} + void set_reps(app_ref_vector &v) {m_reps.reset(); m_reps.append(v);} + + void set_trans(expr_ref &v) {m_trans=v;} + expr* trans() const {return m_trans;} + bool is_init() const {return m_rule.get_uninterpreted_tail_size() == 0;} + }; + + class pt_rules { + typedef obj_map rule2ptrule; + typedef obj_map tag2ptrule; + typedef rule2ptrule::iterator iterator; + rule2ptrule m_rules; + tag2ptrule m_tags; + public: + pt_rules() {} + ~pt_rules() {for (auto &kv : m_rules) {dealloc(kv.m_value);}} + + bool find_by_rule(const datalog::rule &r, pt_rule* &ptr) { + return m_rules.find(&r, ptr); + } + bool find_by_tag(const expr* tag, pt_rule* &ptr) { + return m_tags.find(tag, ptr); + } + pt_rule &mk_rule(ast_manager &m, const datalog::rule &r) { + return mk_rule(pt_rule(m, r)); + } + pt_rule &mk_rule(const pt_rule &v); + void set_tag(expr* tag, pt_rule &v) { + pt_rule *p; + VERIFY(find_by_rule(v.rule(), p)); + p->set_tag(tag); + m_tags.insert(tag, p); + } + + bool empty() {return m_rules.empty();} + iterator begin() {return m_rules.begin();} + iterator end() {return m_rules.end();} + + }; + + manager& pm; // spacer::manager + ast_manager& m; // ast_manager + context& ctx; // spacer::context + + func_decl_ref m_head; // predicate + func_decl_ref_vector m_sig; // signature + ptr_vector m_use; // places where 'this' is referenced. + pt_rules m_pt_rules; // pt rules used to derive transformer + ptr_vector m_rules; // rules used to derive transformer + scoped_ptr m_solver; // solver context + ref m_reach_solver; // context for reachability facts + pobs m_pobs; // proof obligations created so far + frames m_frames; // frames with lemmas + reach_fact_ref_vector m_reach_facts; // reach facts + unsigned m_rf_init_sz; // number of reach fact from INIT + expr_ref_vector m_transition_clause; // extra clause for trans + expr_ref m_transition; // transition relation + expr_ref m_init; // initial condition + app_ref m_extend_lit0; // first literal used to extend initial state + app_ref m_extend_lit; // current literal to extend initial state bool m_all_init; // true if the pt has no uninterpreted body in any rule - ptr_vector m_predicates; + ptr_vector m_predicates; // temp vector used with find_predecessors() stats m_stats; stopwatch m_initialize_watch; stopwatch m_must_reachable_watch; - - - - /// Auxiliary variables to represent different disjunctive - /// cases of must summaries. Stored over 'n' (a.k.a. new) - /// versions of the variables - expr_ref_vector m_reach_case_vars; + stopwatch m_ctp_watch; + stopwatch m_mbp_watch; void init_sig(); + app_ref mk_extend_lit(); void ensure_level(unsigned level); - void add_lemma_core (lemma *lemma); - void add_lemma_from_child (pred_transformer &child, lemma *lemma, unsigned lvl); + void add_lemma_core (lemma *lemma, bool ground_only = false); + void add_lemma_from_child (pred_transformer &child, lemma *lemma, + unsigned lvl, bool ground_only = false); void mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result); // Initialization - void init_rules(decl2rel const& pts, expr_ref& init, expr_ref& transition); - void init_rule(decl2rel const& pts, datalog::rule const& rule, vector& is_init, - ptr_vector& rules, expr_ref_vector& transition); - void init_atom(decl2rel const& pts, app * atom, app_ref_vector& var_reprs, expr_ref_vector& conj, unsigned tail_idx); + void init_rules(decl2rel const& pts); + void init_rule(decl2rel const& pts, datalog::rule const& rule); + void init_atom(decl2rel const& pts, app * atom, app_ref_vector& var_reprs, + expr_ref_vector& side, unsigned tail_idx); void simplify_formulas(tactic& tac, expr_ref_vector& fmls); - // Debugging - bool check_filled(app_ref_vector const& v) const; - void add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r); - expr* mk_fresh_reach_case_var (); + app_ref mk_fresh_rf_tag (); public: pred_transformer(context& ctx, manager& pm, func_decl* head); - ~pred_transformer(); + ~pred_transformer() {} inline bool use_native_mbp (); - reach_fact *get_reach_fact (expr *v) - { - for (unsigned i = 0, sz = m_reach_facts.size (); i < sz; ++i) - if(v == m_reach_facts [i]->get()) { return m_reach_facts[i]; } - return nullptr; + reach_fact *get_rf (expr *v) { + for (auto *rf : m_reach_facts) { + if (v == rf->get()) {return rf;} } + return nullptr; + } + void find_predecessors(datalog::rule const& r, ptr_vector& predicates) const; - void add_rule(datalog::rule* r) { m_rules.push_back(r); } - void add_use(pred_transformer* pt) { if(!m_use.contains(pt)) { m_use.insert(pt); } } + void add_rule(datalog::rule* r) {m_rules.push_back(r);} + void add_use(pred_transformer* pt) {if (!m_use.contains(pt)) {m_use.insert(pt);}} void initialize(decl2rel const& pts); - func_decl* head() const { return m_head; } - ptr_vector const& rules() const { return m_rules; } - func_decl* sig(unsigned i) const { return m_sig[i]; } // signature - func_decl* const* sig() { return m_sig.c_ptr(); } - unsigned sig_size() const { return m_sig.size(); } - expr* transition() const { return m_transition; } - expr* initial_state() const { return m_initial_state; } - expr* rule2tag(datalog::rule const* r) { return m_rule2tag.find(r); } - unsigned get_num_levels() { return m_frames.size (); } + func_decl* head() const {return m_head;} + ptr_vector const& rules() const {return m_rules;} + func_decl* sig(unsigned i) const {return m_sig[i];} // signature + func_decl* const* sig() {return m_sig.c_ptr();} + unsigned sig_size() const {return m_sig.size();} + expr* transition() const {return m_transition;} + expr* init() const {return m_init;} + expr* rule2tag(datalog::rule const* r) { + pt_rule *p; + return m_pt_rules.find_by_rule(*r, p) ? p->tag() : nullptr; + } + unsigned get_num_levels() const {return m_frames.size ();} expr_ref get_cover_delta(func_decl* p_orig, int level); void add_cover(unsigned level, expr* property); - expr_ref get_reachable (); + expr_ref get_reachable(); std::ostream& display(std::ostream& strm) const; void collect_statistics(statistics& st) const; void reset_statistics(); - bool is_must_reachable (expr* state, model_ref* model = nullptr); + bool is_must_reachable(expr* state, model_ref* model = nullptr); /// \brief Returns reachability fact active in the given model /// all determines whether initial reachability facts are included as well - reach_fact *get_used_reach_fact (model_evaluator_util& mev, bool all = true); + reach_fact *get_used_rf(model& mdl, bool all = true); /// \brief Returns reachability fact active in the origin of the given model - reach_fact* get_used_origin_reach_fact (model_evaluator_util &mev, unsigned oidx); - expr_ref get_origin_summary (model_evaluator_util &mev, - unsigned level, unsigned oidx, bool must, - const ptr_vector **aux); + reach_fact* get_used_origin_rf(model &mdl, unsigned oidx); + expr_ref get_origin_summary(model &mdl, + unsigned level, unsigned oidx, bool must, + const ptr_vector **aux); - void remove_predecessors(expr_ref_vector& literals); - void find_predecessors(datalog::rule const& r, ptr_vector& predicates) const; - void find_predecessors(vector >& predicates) const; - datalog::rule const* find_rule(model &mev, bool& is_concrete, + bool is_ctp_blocked(lemma *lem); + const datalog::rule *find_rule(model &mdl); + const datalog::rule *find_rule(model &mev, bool& is_concrete, vector& reach_pred_used, unsigned& num_reuse_reach); - expr* get_transition(datalog::rule const& r) { return m_rule2transition.find(&r); } - ptr_vector& get_aux_vars(datalog::rule const& r) { return m_rule2vars.find(&r); } + expr* get_transition(datalog::rule const& r) { + pt_rule *p; + return m_pt_rules.find_by_rule(r, p) ? p->trans() : nullptr; + } + ptr_vector& get_aux_vars(datalog::rule const& r) { + pt_rule *p = nullptr; + VERIFY(m_pt_rules.find_by_rule(r, p)); + return p->auxs(); + } bool propagate_to_next_level(unsigned level); void propagate_to_infinity(unsigned level); @@ -373,13 +468,14 @@ public: bool add_lemma(expr * lemma, unsigned lvl); bool add_lemma(lemma* lem) {return m_frames.add_lemma(lem);} expr* get_reach_case_var (unsigned idx) const; - bool has_reach_facts () const { return !m_reach_facts.empty () ;} + bool has_rfs () const { return !m_reach_facts.empty () ;} /// initialize reachability facts using initial rules - void init_reach_facts (); - void add_reach_fact (reach_fact *fact); // add reachability fact - reach_fact* get_last_reach_fact () const { return m_reach_facts.back (); } - expr* get_last_reach_case_var () const; + void init_rfs (); + reach_fact *mk_rf(pob &n, model &mdl, const datalog::rule &r); + void add_rf (reach_fact *fact); // add reachability fact + reach_fact* get_last_rf () const { return m_reach_facts.back (); } + expr* get_last_rf_tag () const; pob* mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, app_ref_vector const &b){ @@ -396,22 +492,30 @@ public: datalog::rule const*& r, vector& reach_pred_used, unsigned& num_reuse_reach); - bool is_invariant(unsigned level, expr* lemma, - unsigned& solver_level, expr_ref_vector* core = nullptr); - bool check_inductive(unsigned level, expr_ref_vector& state, - unsigned& assumes_level); + bool is_invariant(unsigned level, lemma* lem, + unsigned& solver_level, + expr_ref_vector* core = nullptr); - expr_ref get_formulas(unsigned level, bool add_axioms); + bool is_invariant(unsigned level, expr* lem, + unsigned& solver_level, expr_ref_vector* core = nullptr) { + // XXX only needed for legacy_frames to compile + UNREACHABLE(); return false; + } + + bool check_inductive(unsigned level, expr_ref_vector& state, + unsigned& assumes_level, unsigned weakness = UINT_MAX); + + expr_ref get_formulas(unsigned level) const; void simplify_formulas(); context& get_context () const {return ctx;} - manager& get_manager() const { return pm; } - ast_manager& get_ast_manager() const { return m; } + manager& get_manager() const {return pm;} + ast_manager& get_ast_manager() const {return m;} void add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r); - void inherit_properties(pred_transformer& other); + void inherit_lemmas(pred_transformer& other); void ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector& aux_vars, bool is_init); @@ -424,6 +528,19 @@ public: /// \brief Returns true if the obligation is already blocked by current quantified lemmas bool is_qblocked (pob &n); + /// \brief interface to Model Based Projection + void mbp(app_ref_vector &vars, expr_ref &fml, model &mdl, + bool reduce_all_selects, bool force = false); + + void updt_solver(prop_solver *solver); + + void updt_solver_with_lemmas(prop_solver *solver, + const pred_transformer &pt, + app *rule_tag, unsigned pos); + void update_solver_with_rfs(prop_solver *solver, + const pred_transformer &pt, + app *rule_tag, unsigned pos); + }; @@ -458,12 +575,20 @@ class pob { /// derivation representing the position of this node in the parent's rule scoped_ptr m_derivation; + /// pobs created as children of this pob (at any time, not + /// necessarily currently active) ptr_vector m_kids; + // lemmas created to block this pob (at any time, not necessarily active) + ptr_vector m_lemmas; + + // depth -> watch + std::map m_expand_watches; + unsigned m_blocked_lvl; public: pob (pob* parent, pred_transformer& pt, unsigned level, unsigned depth=0, bool add_to_parent=true); - ~pob() {if(m_parent) { m_parent->erase_child(*this); }} + ~pob() {if (m_parent) { m_parent->erase_child(*this); }} unsigned weakness() {return m_weakness;} void bump_weakness() {m_weakness++;} @@ -488,16 +613,18 @@ public: unsigned level () const { return m_level; } unsigned depth () const {return m_depth;} + unsigned width () const {return m_kids.size();} + unsigned blocked_at(unsigned lvl=0){return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); } bool use_farkas_generalizer () const {return m_use_farkas;} void set_farkas_generalizer (bool v) {m_use_farkas = v;} expr* post() const { return m_post.get (); } void set_post(expr *post); - void set_post(expr *post, app_ref_vector const &b); + void set_post(expr *post, app_ref_vector const &binding); /// indicate that a new post should be set for the node - void new_post(expr *post) {if(post != m_post) {m_new_post = post;}} + void new_post(expr *post) {if (post != m_post) {m_new_post = post;}} /// true if the node needs to be updated outside of the priority queue bool is_dirty () {return m_new_post;} /// clean a dirty node @@ -508,23 +635,40 @@ public: bool is_closed () const { return !m_open; } void close(); + const ptr_vector &children() {return m_kids;} void add_child (pob &v) {m_kids.push_back (&v);} void erase_child (pob &v) {m_kids.erase (&v);} - bool is_ground () { return m_binding.empty (); } + const ptr_vector &lemmas() {return m_lemmas;} + void add_lemma(lemma* new_lemma) {m_lemmas.push_back(new_lemma);} + + bool is_ground () const { return m_binding.empty (); } + unsigned get_free_vars_size() const { return m_binding.size(); } app_ref_vector const &get_binding() const {return m_binding;} /* - * Return skolem variables that appear in post + * Returns a map from variable id to skolems that implicitly + * represent them in the pob. Note that only some (or none) of the + * skolems returned actually appear in the post of the pob. */ void get_skolems(app_ref_vector& v); - void inc_ref () {++m_ref_count;} - void dec_ref () - { - --m_ref_count; - if(m_ref_count == 0) { dealloc(this); } - } + void on_expand() { m_expand_watches[m_depth].start(); if (m_parent.get()){m_parent.get()->on_expand();} } + void off_expand() { m_expand_watches[m_depth].stop(); if (m_parent.get()){m_parent.get()->off_expand();} }; + double get_expand_time(unsigned depth) { return m_expand_watches[depth].get_seconds();} + void inc_ref () {++m_ref_count;} + void dec_ref () { + --m_ref_count; + if (m_ref_count == 0) {dealloc(this);} + } + + class on_expand_event + { + pob &m_p; + public: + on_expand_event(pob &p) : m_p(p) {m_p.on_expand();} + ~on_expand_event() {m_p.off_expand();} + }; }; @@ -546,7 +690,7 @@ struct pob_ref_gt : {return gt (n1.get (), n2.get ());} }; - +inline unsigned lemma::weakness() {return m_pob ? m_pob->weakness() : UINT_MAX;} /** */ class derivation { @@ -566,16 +710,16 @@ class derivation { const ptr_vector *aux_vars = nullptr); premise (const premise &p); - bool is_must () {return m_must;} - expr * get_summary () {return m_summary.get ();} - app_ref_vector &get_ovars () {return m_ovars;} - unsigned get_oidx () {return m_oidx;} - pred_transformer &pt () {return m_pt;} + bool is_must() {return m_must;} + expr * get_summary() {return m_summary.get ();} + app_ref_vector &get_ovars() {return m_ovars;} + unsigned get_oidx() {return m_oidx;} + pred_transformer &pt() {return m_pt;} /// \brief Updated the summary. /// The new summary is over n-variables. - void set_summary (expr * summary, bool must, - const ptr_vector *aux_vars = nullptr); + void set_summary(expr * summary, bool must, + const ptr_vector *aux_vars = nullptr); }; @@ -595,7 +739,9 @@ class derivation { app_ref_vector m_evars; /// -- create next child using given model as the guide /// -- returns NULL if there is no next child - pob* create_next_child (model_evaluator_util &mev); + pob* create_next_child (model &mdl); + /// existentially quantify vars and skolemize the result + void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res); public: derivation (pob& parent, datalog::rule const& rule, expr *trans, app_ref_vector const &evars); @@ -605,7 +751,7 @@ public: /// creates the first child. Must be called after all the premises /// are added. The model must be valid for the premises /// Returns NULL if no child exits - pob *create_first_child (model_evaluator_util &mev); + pob *create_first_child (model &mdl); /// Create the next child. Must summary of the currently active /// premise must be consistent with the transition relation @@ -616,6 +762,7 @@ public: ast_manager &get_ast_manager () const {return m_parent.get_ast_manager ();} manager &get_manager () const {return m_parent.get_manager ();} context &get_context() const {return m_parent.get_context();} + pred_transformer &pt() const {return m_parent.pt();} }; @@ -634,24 +781,22 @@ public: void reset(); pob * top (); void pop () {m_obligations.pop ();} - void push (pob &n) {m_obligations.push (&n);} + void push (pob &n); - void inc_level () - { - SASSERT (!m_obligations.empty () || m_root); - m_max_level++; - m_min_depth++; - if(m_root && m_obligations.empty()) { m_obligations.push(m_root); } - } + void inc_level () { + SASSERT (!m_obligations.empty () || m_root); + m_max_level++; + m_min_depth++; + if (m_root && m_obligations.empty()) { m_obligations.push(m_root); } + } - pob& get_root() const { return *m_root.get (); } + pob& get_root() const {return *m_root.get ();} void set_root(pob& n); bool is_root (pob& n) const {return m_root.get () == &n;} - unsigned max_level () {return m_max_level;} - unsigned min_depth () {return m_min_depth;} - size_t size () {return m_obligations.size ();} - + unsigned max_level() const {return m_max_level;} + unsigned min_depth() const {return m_min_depth;} + size_t size() const {return m_obligations.size();} }; @@ -670,18 +815,55 @@ public: }; +class spacer_callback { +protected: + context &m_context; + +public: + spacer_callback(context &context) : m_context(context) {} + + virtual ~spacer_callback() = default; + + context &get_context() { return m_context; } + + virtual inline bool new_lemma() { return false; } + + virtual void new_lemma_eh(expr *lemma, unsigned level) {} + + virtual inline bool predecessor() { return false; } + + virtual void predecessor_eh() {} + + virtual inline bool unfold() { return false; } + + virtual void unfold_eh() {} + + virtual inline bool propagate() { return false; } + + virtual void propagate_eh() {} + +}; + +// order in which children are processed +enum spacer_children_order { + CO_RULE, // same order as in the rule + CO_REV_RULE, // reverse order of the rule + CO_RANDOM // random shuffle +}; + class context { struct stats { unsigned m_num_queries; - unsigned m_num_reach_queries; unsigned m_num_reuse_reach; unsigned m_max_query_lvl; unsigned m_max_depth; unsigned m_cex_depth; - unsigned m_expand_node_undef; + unsigned m_expand_pob_undef; unsigned m_num_lemmas; unsigned m_num_restarts; + unsigned m_num_lemmas_imported; + unsigned m_num_lemmas_discarded; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; @@ -694,10 +876,19 @@ class context { stopwatch m_create_children_watch; stopwatch m_init_rules_watch; - fixedpoint_params const& m_params; + fp_params const& m_params; ast_manager& m; datalog::context* m_context; manager m_pm; + + // three solver pools for different queries + scoped_ptr m_pool0; + scoped_ptr m_pool1; + scoped_ptr m_pool2; + + + random_gen m_random; + spacer_children_order m_children_order; decl2rel m_rels; // Map from relation predicate to fp-operator. func_decl_ref m_query_pred; pred_transformer* m_query; @@ -710,27 +901,64 @@ class context { model_converter_ref m_mc; proof_converter_ref m_pc; bool m_use_native_mbp; - bool m_ground_cti; bool m_instantiate; bool m_use_qlemmas; bool m_weak_abs; bool m_use_restarts; + bool m_simplify_pob; + bool m_use_euf_gen; + bool m_use_ctp; + bool m_use_inc_clause; + bool m_blast_term_ite; + bool m_reuse_pobs; + bool m_use_ind_gen; + bool m_use_array_eq_gen; + bool m_validate_lemmas; + bool m_use_propagate; + bool m_reset_obligation_queue; + bool m_push_pob; + bool m_use_lemma_as_pob; + bool m_elim_aux; + bool m_reach_dnf; + bool m_use_derivations; + bool m_validate_result; + bool m_use_eq_prop; + bool m_ground_pob; + bool m_q3_qgen; + bool m_use_gpdr; + bool m_simplify_formulas_pre; + bool m_simplify_formulas_post; + bool m_pdr_bfs; + unsigned m_push_pob_max_depth; + unsigned m_max_level; unsigned m_restart_initial_threshold; + scoped_ptr_vector m_callbacks; + json_marshaller m_json_marshaller; + + // Solve using gpdr strategy + lbool gpdr_solve_core(); + bool gpdr_check_reachability(unsigned lvl, model_search &ms); + bool gpdr_create_split_children(pob &n, const datalog::rule &r, + expr *trans, + model &mdl, + pob_ref_buffer &out); // Functions used by search. - lbool solve_core (unsigned from_lvl = 0); - bool check_reachability (); + lbool solve_core(unsigned from_lvl = 0); + bool is_requeue(pob &n); + bool check_reachability(); bool propagate(unsigned min_prop_lvl, unsigned max_prop_lvl, unsigned full_prop_lvl); bool is_reachable(pob &n); - lbool expand_node(pob& n); - reach_fact *mk_reach_fact (pob& n, model_evaluator_util &mev, - datalog::rule const& r); - bool create_children(pob& n, datalog::rule const& r, - model_evaluator_util &model, - const vector& reach_pred_used); + lbool expand_pob(pob &n, pob_ref_buffer &out); + bool create_children(pob& n, const datalog::rule &r, + model &mdl, + const vector& reach_pred_used, + pob_ref_buffer &out); + expr_ref mk_sat_answer(); expr_ref mk_unsat_answer() const; + unsigned get_cex_depth (); // Generate inductive property void get_level_property(unsigned lvl, expr_ref_vector& res, @@ -738,102 +966,112 @@ class context { // Initialization - void init_lemma_generalizers(datalog::rule_set& rules); + void init_lemma_generalizers(); + void reset_lemma_generalizers(); + void inherit_lemmas(const decl2rel& rels); + void init_global_smt_params(); + void init_rules(datalog::rule_set& rules, decl2rel& transformers); + // (re)initialize context with new relations + void init(const decl2rel &rels); + bool validate(); bool check_invariant(unsigned lvl); bool check_invariant(unsigned lvl, func_decl* fn); void checkpoint(); - void init_rules(datalog::rule_set& rules, decl2rel& transformers); - void simplify_formulas(); - void reset_lemma_generalizers(); + void dump_json(); - bool validate(); - - unsigned get_cex_depth (); + void predecessor_eh(); + void updt_params(); public: /** Initial values of predicates are stored in corresponding relations in dctx. - We check whether there is some reachable state of the relation checked_relation. */ - context( - fixedpoint_params const& params, - ast_manager& m); - + context(fp_params const& params, ast_manager& m); ~context(); - fixedpoint_params const& get_params() const { return m_params; } - bool use_native_mbp () {return m_use_native_mbp;} - bool use_ground_cti () {return m_ground_cti;} - bool use_instantiate () { return m_instantiate; } - bool use_qlemmas () {return m_use_qlemmas; } - ast_manager& get_ast_manager() const { return m; } - manager& get_manager() { return m_pm; } - decl2rel const& get_pred_transformers() const { return m_rels; } - pred_transformer& get_pred_transformer(func_decl* p) const - { return *m_rels.find(p); } - datalog::context& get_datalog_context() const - { SASSERT(m_context); return *m_context; } + const fp_params &get_params() const { return m_params; } + bool use_native_mbp () {return m_use_native_mbp;} + bool use_ground_pob () {return m_ground_pob;} + bool use_instantiate () {return m_instantiate;} + bool weak_abs() {return m_weak_abs;} + bool use_qlemmas () {return m_use_qlemmas;} + bool use_euf_gen() {return m_use_euf_gen;} + bool simplify_pob() {return m_simplify_pob;} + bool use_ctp() {return m_use_ctp;} + bool use_inc_clause() {return m_use_inc_clause;} + bool blast_term_ite() {return m_blast_term_ite;} + bool reuse_pobs() {return m_reuse_pobs;} + bool elim_aux() {return m_elim_aux;} + bool reach_dnf() {return m_reach_dnf;} + + ast_manager& get_ast_manager() const {return m;} + manager& get_manager() {return m_pm;} + decl2rel const& get_pred_transformers() const {return m_rels;} + pred_transformer& get_pred_transformer(func_decl* p) const {return *m_rels.find(p);} + + datalog::context& get_datalog_context() const { + SASSERT(m_context); return *m_context; + } + + void update_rules(datalog::rule_set& rules); + lbool solve(unsigned from_lvl = 0); + lbool solve_from_lvl (unsigned from_lvl); + + expr_ref get_answer(); /** * get bottom-up (from query) sequence of ground predicate instances * (for e.g. P(0,1,0,0,3)) that together form a ground derivation to query */ - expr_ref get_ground_sat_answer (); + expr_ref get_ground_sat_answer (); + proof_ref get_ground_refutation(); + void get_rules_along_trace (datalog::rule_ref_vector& rules); void collect_statistics(statistics& st) const; void reset_statistics(); - - std::ostream& display(std::ostream& strm) const; - - void display_certificate(std::ostream& strm) const {} - - lbool solve(unsigned from_lvl = 0); - - lbool solve_from_lvl (unsigned from_lvl); - void reset(); - void set_query(func_decl* q) { m_query_pred = q; } - - void set_unsat() { m_last_result = l_false; } - - void set_model_converter(model_converter_ref& mc) { m_mc = mc; } - - void get_rules_along_trace (datalog::rule_ref_vector& rules); + std::ostream& display(std::ostream& out) const; + void display_certificate(std::ostream& out) const {NOT_IMPLEMENTED_YET();} + pob& get_root() const {return m_pob_queue.get_root();} + void set_query(func_decl* q) {m_query_pred = q;} + void set_unsat() {m_last_result = l_false;} + void set_model_converter(model_converter_ref& mc) {m_mc = mc;} model_converter_ref get_model_converter() { return m_mc; } - void set_proof_converter(proof_converter_ref& pc) { m_pc = pc; } - - void update_rules(datalog::rule_set& rules); - - void set_axioms(expr* axioms) { m_pm.set_background(axioms); } + scoped_ptr_vector &callbacks() {return m_callbacks;} unsigned get_num_levels(func_decl* p); expr_ref get_cover_delta(int level, func_decl* p_orig, func_decl* p); - void add_cover(int level, func_decl* pred, expr* property); - expr_ref get_reachable (func_decl* p); - void add_invariant (func_decl *pred, expr* property); - model_ref get_model(); - proof_ref get_proof() const; - pob& get_root() const { return m_pob_queue.get_root(); } - expr_ref get_constraints (unsigned lvl); - void add_constraints (unsigned lvl, const expr_ref& c); + void add_constraint (expr *c, unsigned lvl); + + void new_lemma_eh(pred_transformer &pt, lemma *lem); + void new_pob_eh(pob *p); + + bool is_inductive(); + + + // three different solvers with three different sets of parameters + // different solvers are used for different types of queries in spacer + solver* mk_solver0() {return m_pool0->mk_solver();} + solver* mk_solver1() {return m_pool1->mk_solver();} + solver* mk_solver2() {return m_pool2->mk_solver();} }; inline bool pred_transformer::use_native_mbp () {return ctx.use_native_mbp ();} diff --git a/src/muz/spacer/spacer_dl_interface.cpp b/src/muz/spacer/spacer_dl_interface.cpp index 52209454d..807e9b751 100644 --- a/src/muz/spacer/spacer_dl_interface.cpp +++ b/src/muz/spacer/spacer_dl_interface.cpp @@ -34,6 +34,7 @@ Revision History: #include "model/model_smt2_pp.h" #include "ast/scoped_proof.h" #include "muz/transforms/dl_transforms.h" +#include "muz/spacer/spacer_callback.h" using namespace spacer; @@ -92,19 +93,14 @@ lbool dl_interface::query(expr * query) datalog::rule_set old_rules(rules0); func_decl_ref query_pred(m); rm.mk_query(query, m_ctx.get_rules()); - expr_ref bg_assertion = m_ctx.get_background_assertion(); check_reset(); TRACE("spacer", - if (!m.is_true(bg_assertion)) { - tout << "axioms:\n"; - tout << mk_pp(bg_assertion, m) << "\n"; - } - tout << "query: " << mk_pp(query, m) << "\n"; - tout << "rules:\n"; - m_ctx.display_rules(tout); - ); + tout << "query: " << mk_pp(query, m) << "\n"; + tout << "rules:\n"; + m_ctx.display_rules(tout); + ); apply_default_transformation(m_ctx); @@ -160,7 +156,6 @@ lbool dl_interface::query(expr * query) m_context->set_proof_converter(m_ctx.get_proof_converter()); m_context->set_model_converter(m_ctx.get_model_converter()); m_context->set_query(query_pred); - m_context->set_axioms(bg_assertion); m_context->update_rules(m_spacer_rules); if (m_spacer_rules.get_rules().empty()) { @@ -169,7 +164,7 @@ lbool dl_interface::query(expr * query) return l_false; } - return m_context->solve(); + return m_context->solve(m_ctx.get_params().spacer_min_level()); } @@ -254,7 +249,6 @@ lbool dl_interface::query_from_lvl(expr * query, unsigned lvl) m_context->set_proof_converter(m_ctx.get_proof_converter()); m_context->set_model_converter(m_ctx.get_model_converter()); m_context->set_query(query_pred); - m_context->set_axioms(bg_assertion); m_context->update_rules(m_spacer_rules); if (m_spacer_rules.get_rules().empty()) { @@ -352,3 +346,14 @@ proof_ref dl_interface::get_proof() { return m_context->get_proof(); } + +void dl_interface::add_callback(void *state, + const datalog::t_new_lemma_eh new_lemma_eh, + const datalog::t_predecessor_eh predecessor_eh, + const datalog::t_unfold_eh unfold_eh){ + m_context->callbacks().push_back(alloc(user_callback, *m_context, state, new_lemma_eh, predecessor_eh, unfold_eh)); +} + +void dl_interface::add_constraint (expr *c, unsigned lvl){ + m_context->add_constraint(c, lvl); +} diff --git a/src/muz/spacer/spacer_dl_interface.h b/src/muz/spacer/spacer_dl_interface.h index e5a41427d..9fc60dcf0 100644 --- a/src/muz/spacer/spacer_dl_interface.h +++ b/src/muz/spacer/spacer_dl_interface.h @@ -79,6 +79,13 @@ public: proof_ref get_proof() override; + void add_callback(void *state, + const datalog::t_new_lemma_eh new_lemma_eh, + const datalog::t_predecessor_eh predecessor_eh, + const datalog::t_unfold_eh unfold_eh) override; + + void add_constraint (expr *c, unsigned lvl) override; + }; } diff --git a/src/muz/spacer/spacer_farkas_learner.h b/src/muz/spacer/spacer_farkas_learner.h index 724b18b73..0e696f4f1 100644 --- a/src/muz/spacer/spacer_farkas_learner.h +++ b/src/muz/spacer/spacer_farkas_learner.h @@ -24,12 +24,6 @@ Revision History: namespace spacer { - - - - - - class farkas_learner { typedef obj_hashtable expr_set; diff --git a/src/muz/spacer/spacer_generalizers.cpp b/src/muz/spacer/spacer_generalizers.cpp index 19989e440..b0fb6c2d3 100644 --- a/src/muz/spacer/spacer_generalizers.cpp +++ b/src/muz/spacer/spacer_generalizers.cpp @@ -21,11 +21,16 @@ Revision History: #include "muz/spacer/spacer_context.h" #include "muz/spacer/spacer_generalizers.h" +#include "ast/ast_util.h" #include "ast/expr_abstract.h" #include "ast/rewriter/var_subst.h" #include "ast/for_each_expr.h" -#include "ast/factor_equivs.h" - +#include "ast/rewriter/factor_equivs.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "ast/substitution/matcher.h" +#include "ast/expr_functors.h" +#include "smt/smt_solver.h" +#include "qe/qe_term_graph.h" namespace spacer { void lemma_sanity_checker::operator()(lemma_ref &lemma) { @@ -33,9 +38,23 @@ void lemma_sanity_checker::operator()(lemma_ref &lemma) { expr_ref_vector cube(lemma->get_ast_manager()); cube.append(lemma->get_cube()); ENSURE(lemma->get_pob()->pt().check_inductive(lemma->level(), - cube, uses_level)); + cube, uses_level, + lemma->weakness())); } +namespace{ + class contains_array_op_proc : public i_expr_pred { + ast_manager &m; + family_id m_array_fid; + public: + contains_array_op_proc(ast_manager &manager) : + m(manager), m_array_fid(m.mk_family_id("array")) + {} + virtual bool operator()(expr *e) { + return is_app(e) && to_app(e)->get_family_id() == m_array_fid; + } + }; +} // ------------------------ // lemma_bool_inductive_generalizer @@ -50,6 +69,9 @@ void lemma_bool_inductive_generalizer::operator()(lemma_ref &lemma) { pred_transformer &pt = lemma->get_pob()->pt(); ast_manager &m = pt.get_ast_manager(); + contains_array_op_proc has_array_op(m); + check_pred has_arrays(has_array_op, m); + expr_ref_vector cube(m); cube.append(lemma->get_cube()); @@ -58,14 +80,21 @@ void lemma_bool_inductive_generalizer::operator()(lemma_ref &lemma) { ptr_vector processed; expr_ref_vector extra_lits(m); + unsigned weakness = lemma->weakness(); + unsigned i = 0, num_failures = 0; while (i < cube.size() && (!m_failure_limit || num_failures < m_failure_limit)) { expr_ref lit(m); lit = cube.get(i); + if (m_array_only && !has_arrays(lit)) { + processed.push_back(lit); + ++i; + continue; + } cube[i] = true_expr; if (cube.size() > 1 && - pt.check_inductive(lemma->level(), cube, uses_level)) { + pt.check_inductive(lemma->level(), cube, uses_level, weakness)) { num_failures = 0; dirty = true; for (i = 0; i < cube.size() && @@ -82,7 +111,7 @@ void lemma_bool_inductive_generalizer::operator()(lemma_ref &lemma) { SASSERT(extra_lits.size() > 1); for (unsigned j = 0, sz = extra_lits.size(); !found && j < sz; ++j) { cube[i] = extra_lits.get(j); - if (pt.check_inductive(lemma->level(), cube, uses_level)) { + if (pt.check_inductive(lemma->level(), cube, uses_level, weakness)) { num_failures = 0; dirty = true; found = true; @@ -130,10 +159,11 @@ void unsat_core_generalizer::operator()(lemma_ref &lemma) unsigned old_sz = lemma->get_cube().size(); unsigned old_level = lemma->level(); + (void)old_level; unsigned uses_level; expr_ref_vector core(m); - VERIFY(pt.is_invariant(old_level, lemma->get_expr(), uses_level, &core)); + VERIFY(pt.is_invariant(lemma->level(), lemma.get(), uses_level, &core)); CTRACE("spacer", old_sz > core.size(), tout << "unsat core reduced lemma from: " @@ -176,118 +206,131 @@ public: }; } +bool lemma_array_eq_generalizer::is_array_eq (ast_manager &m, expr* e) { + + expr *e1 = nullptr, *e2 = nullptr; + if (m.is_eq(e, e1, e2) && is_app(e1) && is_app(e2)) { + app *a1 = to_app(e1); + app *a2 = to_app(e2); + array_util au(m); + if (a1->get_family_id() == null_family_id && + a2->get_family_id() == null_family_id && + au.is_array(a1) && au.is_array(a2)) + return true; + } + return false; +} + void lemma_array_eq_generalizer::operator() (lemma_ref &lemma) { - TRACE("core_array_eq", tout << "Looking for equalities\n";); - // -- find array constants ast_manager &m = lemma->get_ast_manager(); - manager &pm = m_ctx.get_manager(); - (void)pm; + expr_ref_vector core(m); expr_ref v(m); func_decl_set symb; collect_array_proc cap(m, symb); + + // -- find array constants core.append (lemma->get_cube()); v = mk_and(core); for_each_expr(cap, v); - TRACE("core_array_eq", + CTRACE("core_array_eq", symb.size() > 1 && symb.size() <= 8, tout << "found " << symb.size() << " array variables in: \n" - << mk_pp(v, m) << "\n";); + << v << "\n";); - // too few constants - if (symb.size() <= 1) { return; } - // too many constants, skip this - if (symb.size() >= 8) { return; } + // too few constants or too many constants + if (symb.size() <= 1 || symb.size() > 8) { return; } - // -- for every pair of variables, try an equality - typedef func_decl_set::iterator iterator; + // -- for every pair of constants (A, B), check whether the + // -- equality (A=B) generalizes a literal in the lemma + ptr_vector vsymbs; - for (iterator it = symb.begin(), end = symb.end(); - it != end; ++it) - { vsymbs.push_back(*it); } + for (auto * fdecl : symb) {vsymbs.push_back(fdecl);} + // create all equalities expr_ref_vector eqs(m); + for (unsigned i = 0, sz = vsymbs.size(); i < sz; ++i) { + for (unsigned j = i + 1; j < sz; ++j) { + eqs.push_back(m.mk_eq(m.mk_const(vsymbs.get(i)), + m.mk_const(vsymbs.get(j)))); + } + } - for (unsigned i = 0, sz = vsymbs.size(); i < sz; ++i) - for (unsigned j = i + 1; j < sz; ++j) - { eqs.push_back(m.mk_eq(m.mk_const(vsymbs.get(i)), - m.mk_const(vsymbs.get(j)))); } - - smt::kernel solver(m, m_ctx.get_manager().fparams2()); + // smt-solver to check whether a literal is generalized. using + // default params. There has to be a simpler way to approximate + // this check + ref sol = mk_smt_solver(m, params_ref::get_empty(), symbol::null); + // literals of the new lemma expr_ref_vector lits(m); - for (unsigned i = 0, core_sz = core.size(); i < core_sz; ++i) { - SASSERT(lits.size() == i); - solver.push(); - solver.assert_expr(core.get(i)); - for (unsigned j = 0, eqs_sz = eqs.size(); j < eqs_sz; ++j) { - solver.push(); - solver.assert_expr(eqs.get(j)); - lbool res = solver.check(); - solver.pop(1); + lits.append(core); + expr *t = nullptr; + bool dirty = false; + for (unsigned i = 0, sz = core.size(); i < sz; ++i) { + // skip a literal is it is already an array equality + if (m.is_not(lits.get(i), t) && is_array_eq(m, t)) continue; + solver::scoped_push _pp_(*sol); + sol->assert_expr(lits.get(i)); + for (auto *e : eqs) { + solver::scoped_push _p_(*sol); + sol->assert_expr(e); + lbool res = sol->check_sat(0, nullptr); if (res == l_false) { TRACE("core_array_eq", - tout << "strengthened " << mk_pp(core.get(i), m) - << " with " << mk_pp(m.mk_not(eqs.get(j)), m) << "\n";); - lits.push_back(m.mk_not(eqs.get(j))); + tout << "strengthened " << mk_pp(lits.get(i), m) + << " with " << mk_pp(mk_not(m, e), m) << "\n";); + lits[i] = mk_not(m, e); + dirty = true; break; } } - solver.pop(1); - if (lits.size() == i) { lits.push_back(core.get(i)); } } - /** - HACK: if the first 3 arguments of pt are boolean, assume - they correspond to SeaHorn encoding and condition the equality on them. - */ - // pred_transformer &pt = n.pt (); - // if (pt.sig_size () >= 3 && - // m.is_bool (pt.sig (0)->get_range ()) && - // m.is_bool (pt.sig (1)->get_range ()) && - // m.is_bool (pt.sig (2)->get_range ())) - // { - // lits.push_back (m.mk_const (pm.o2n(pt.sig (0), 0))); - // lits.push_back (m.mk_not (m.mk_const (pm.o2n(pt.sig (1), 0)))); - // lits.push_back (m.mk_not (m.mk_const (pm.o2n(pt.sig (2), 0)))); - // } + // nothing changed + if (!dirty) return; - TRACE("core_array_eq", tout << "new possible core " - << mk_pp(pm.mk_and(lits), m) << "\n";); + TRACE("core_array_eq", + tout << "new possible core " << mk_and(lits) << "\n";); pred_transformer &pt = lemma->get_pob()->pt(); - // -- check if it is consistent with the transition relation + // -- check if the generalized result is consistent with trans unsigned uses_level1; - if (pt.check_inductive(lemma->level(), lits, uses_level1)) { + if (pt.check_inductive(lemma->level(), lits, uses_level1, lemma->weakness())) { TRACE("core_array_eq", tout << "Inductive!\n";); - lemma->update_cube(lemma->get_pob(),lits); + lemma->update_cube(lemma->get_pob(), lits); lemma->set_level(uses_level1); - return; - } else - { TRACE("core_array_eq", tout << "Not-Inductive!\n";);} + } + else + {TRACE("core_array_eq", tout << "Not-Inductive!\n";);} } void lemma_eq_generalizer::operator() (lemma_ref &lemma) { TRACE("core_eq", tout << "Transforming equivalence classes\n";); - ast_manager &m = m_ctx.get_ast_manager(); - expr_ref_vector core(m); - core.append (lemma->get_cube()); + if (lemma->get_cube().empty()) return; - bool dirty; - expr_equiv_class eq_classes(m); - factor_eqs(core, eq_classes); - // create all possible equalities to allow for simple inductive generalization - dirty = equiv_to_expr_full(eq_classes, core); - if (dirty) { + ast_manager &m = m_ctx.get_ast_manager(); + qe::term_graph egraph(m); + egraph.add_lits(lemma->get_cube()); + + // -- expand the cube with all derived equalities + expr_ref_vector core(m); + egraph.to_lits(core, true); + + // -- if the core looks different from the original cube + if (core.size() != lemma->get_cube().size() || + core.get(0) != lemma->get_cube().get(0)) { + // -- update the lemma lemma->update_cube(lemma->get_pob(), core); } } + + }; diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h index 1962e74e0..45ae472bd 100644 --- a/src/muz/spacer/spacer_generalizers.h +++ b/src/muz/spacer/spacer_generalizers.h @@ -48,11 +48,14 @@ class lemma_bool_inductive_generalizer : public lemma_generalizer { }; unsigned m_failure_limit; + bool m_array_only; stats m_st; public: - lemma_bool_inductive_generalizer(context& ctx, unsigned failure_limit) : - lemma_generalizer(ctx), m_failure_limit(failure_limit) {} + lemma_bool_inductive_generalizer(context& ctx, unsigned failure_limit, + bool array_only = false) : + lemma_generalizer(ctx), m_failure_limit(failure_limit), + m_array_only(array_only) {} ~lemma_bool_inductive_generalizer() override {} void operator()(lemma_ref &lemma) override; @@ -80,6 +83,8 @@ public: }; class lemma_array_eq_generalizer : public lemma_generalizer { +private: + bool is_array_eq(ast_manager &m, expr *e); public: lemma_array_eq_generalizer(context &ctx) : lemma_generalizer(ctx) {} ~lemma_array_eq_generalizer() override {} @@ -94,6 +99,45 @@ public: void operator()(lemma_ref &lemma) override; }; +class lemma_quantifier_generalizer : public lemma_generalizer { + struct stats { + unsigned count; + unsigned num_failures; + stopwatch watch; + stats() {reset();} + void reset() {count = 0; num_failures = 0; watch.reset();} + }; + ast_manager &m; + arith_util m_arith; + stats m_st; + expr_ref_vector m_cube; + + bool m_normalize_cube; + int m_offset; +public: + lemma_quantifier_generalizer(context &ctx, bool normalize_cube = true); + virtual ~lemma_quantifier_generalizer() {} + virtual void operator()(lemma_ref &lemma); + + virtual void collect_statistics(statistics& st) const; + virtual void reset_statistics() {m_st.reset();} +private: + bool generalize(lemma_ref &lemma, app *term); + + void find_candidates(expr *e, app_ref_vector &candidate); + bool is_ub(var *var, expr *e); + bool is_lb(var *var, expr *e); + void mk_abs_cube (lemma_ref &lemma, app *term, var *var, + expr_ref_vector &gnd_cube, + expr_ref_vector &abs_cube, + expr *&lb, expr *&ub, unsigned &stride); + + bool match_sk_idx(expr *e, app_ref_vector const &zks, expr *&idx, app *&sk); + void cleanup(expr_ref_vector& cube, app_ref_vector const &zks, expr_ref &bind); + + bool find_stride(expr_ref_vector &c, expr_ref &pattern, unsigned &stride); }; +} + #endif diff --git a/src/muz/spacer/spacer_itp_solver.cpp b/src/muz/spacer/spacer_itp_solver.cpp deleted file mode 100644 index 7ca66fbfd..000000000 --- a/src/muz/spacer/spacer_itp_solver.cpp +++ /dev/null @@ -1,355 +0,0 @@ -/** -Copyright (c) 2017 Arie Gurfinkel - -Module Name: - - spacer_itp_solver.cpp - -Abstract: - - A solver that produces interpolated unsat cores - -Author: - - Arie Gurfinkel - -Notes: - ---*/ -#include"muz/spacer/spacer_itp_solver.h" -#include"ast/ast.h" -#include"muz/spacer/spacer_util.h" -#include"muz/spacer/spacer_farkas_learner.h" -#include"ast/rewriter/expr_replacer.h" -#include"muz/spacer/spacer_unsat_core_learner.h" -#include"muz/spacer/spacer_unsat_core_plugin.h" - -namespace spacer { -void itp_solver::push () -{ - m_defs.push_back (def_manager (*this)); - m_solver.push (); -} - -void itp_solver::pop (unsigned n) -{ - m_solver.pop (n); - unsigned lvl = m_defs.size (); - SASSERT (n <= lvl); - unsigned new_lvl = lvl-n; - while (m_defs.size() > new_lvl) { - m_num_proxies -= m_defs.back ().m_defs.size (); - m_defs.pop_back (); - } -} - -app* itp_solver::fresh_proxy () -{ - if (m_num_proxies == m_proxies.size()) { - std::stringstream name; - name << "spacer_proxy!" << m_proxies.size (); - app_ref res(m); - res = m.mk_const (symbol (name.str ().c_str ()), - m.mk_bool_sort ()); - m_proxies.push_back (res); - - // -- add the new proxy to proxy eliminator - proof_ref pr(m); - pr = m.mk_asserted (m.mk_true ()); - m_elim_proxies_sub.insert (res, m.mk_true (), pr); - - } - return m_proxies.get (m_num_proxies++); -} - -app* itp_solver::mk_proxy (expr *v) -{ - { - expr *e = v; - m.is_not (v, e); - if (is_uninterp_const(e)) { return to_app(v); } - } - - def_manager &def = m_defs.size () > 0 ? m_defs.back () : m_base_defs; - return def.mk_proxy (v); -} - -bool itp_solver::mk_proxies (expr_ref_vector &v, unsigned from) -{ - bool dirty = false; - for (unsigned i = from, sz = v.size(); i < sz; ++i) { - app *p = mk_proxy (v.get (i)); - dirty |= (v.get (i) != p); - v[i] = p; - } - return dirty; -} - -void itp_solver::push_bg (expr *e) -{ - if (m_assumptions.size () > m_first_assumption) - { m_assumptions.shrink(m_first_assumption); } - m_assumptions.push_back (e); - m_first_assumption = m_assumptions.size (); -} - -void itp_solver::pop_bg (unsigned n) -{ - if (n == 0) { return; } - - if (m_assumptions.size () > m_first_assumption) - { m_assumptions.shrink(m_first_assumption); } - m_first_assumption = m_first_assumption > n ? m_first_assumption - n : 0; - m_assumptions.shrink (m_first_assumption); -} - -unsigned itp_solver::get_num_bg () {return m_first_assumption;} - -lbool itp_solver::check_sat (unsigned num_assumptions, expr * const *assumptions) -{ - // -- remove any old assumptions - if (m_assumptions.size () > m_first_assumption) - { m_assumptions.shrink(m_first_assumption); } - - // -- replace theory literals in background assumptions with proxies - mk_proxies (m_assumptions); - // -- in case mk_proxies added new literals, they are all background - m_first_assumption = m_assumptions.size (); - - m_assumptions.append (num_assumptions, assumptions); - m_is_proxied = mk_proxies (m_assumptions, m_first_assumption); - - lbool res; - res = m_solver.check_sat (m_assumptions.size (), m_assumptions.c_ptr ()); - set_status (res); - return res; -} - - -app* itp_solver::def_manager::mk_proxy (expr *v) -{ - app* r; - if (m_expr2proxy.find(v, r)) { return r; } - - ast_manager &m = m_parent.m; - app_ref proxy(m); - app_ref def(m); - proxy = m_parent.fresh_proxy (); - def = m.mk_or (m.mk_not (proxy), v); - m_defs.push_back (def); - m_expr2proxy.insert (v, proxy); - m_proxy2def.insert (proxy, def); - - m_parent.assert_expr (def.get ()); - return proxy; -} - -bool itp_solver::def_manager::is_proxy (app *k, app_ref &def) -{ - app *r = nullptr; - bool found = m_proxy2def.find (k, r); - def = r; - return found; -} - -void itp_solver::def_manager::reset () -{ - m_expr2proxy.reset (); - m_proxy2def.reset (); - m_defs.reset (); -} - -bool itp_solver::def_manager::is_proxy_def (expr *v) -{ - // XXX This might not be the most robust way to check - return m_defs.contains (v); -} - -bool itp_solver::is_proxy(expr *e, app_ref &def) -{ - if (!is_uninterp_const(e)) { return false; } - - app *a = to_app (e); - - for (int i = m_defs.size (); i > 0; --i) - if (m_defs[i-1].is_proxy (a, def)) - { return true; } - - if (m_base_defs.is_proxy (a, def)) - { return true; } - - return false; -} - -void itp_solver::collect_statistics (statistics &st) const -{ - m_solver.collect_statistics (st); - st.update ("time.itp_solver.itp_core", m_itp_watch.get_seconds ()); -} - -void itp_solver::reset_statistics () -{ - m_itp_watch.reset (); -} - -void itp_solver::get_unsat_core (ptr_vector &core) -{ - m_solver.get_unsat_core (core); - undo_proxies_in_core (core); -} -void itp_solver::undo_proxies_in_core (ptr_vector &r) -{ - app_ref e(m); - expr_fast_mark1 bg; - for (unsigned i = 0; i < m_first_assumption; ++i) - { bg.mark(m_assumptions.get(i)); } - - // expand proxies - unsigned j = 0; - for (unsigned i = 0, sz = r.size(); i < sz; ++i) { - // skip background assumptions - if (bg.is_marked(r[i])) { continue; } - - // -- undo proxies, but only if they were introduced in check_sat - if (m_is_proxied && is_proxy(r[i], e)) { - SASSERT (m.is_or (e)); - r[j] = e->get_arg (1); - } else if (i != j) { r[j] = r[i]; } - j++; - } - r.shrink (j); -} - -void itp_solver::undo_proxies (expr_ref_vector &r) -{ - app_ref e(m); - // expand proxies - for (unsigned i = 0, sz = r.size (); i < sz; ++i) - if (is_proxy(r.get(i), e)) { - SASSERT (m.is_or (e)); - r[i] = e->get_arg (1); - } -} - -void itp_solver::get_unsat_core (expr_ref_vector &_core) -{ - ptr_vector core; - get_unsat_core (core); - _core.append (core.size (), core.c_ptr ()); -} - -void itp_solver::elim_proxies (expr_ref_vector &v) -{ - expr_ref f = mk_and (v); - scoped_ptr rep = mk_expr_simp_replacer (m); - rep->set_substitution (&m_elim_proxies_sub); - (*rep) (f); - v.reset (); - flatten_and (f, v); -} - -void itp_solver::get_itp_core (expr_ref_vector &core) -{ - scoped_watch _t_ (m_itp_watch); - - typedef obj_hashtable expr_set; - expr_set B; - for (unsigned i = m_first_assumption, sz = m_assumptions.size(); i < sz; ++i) { - expr *a = m_assumptions.get (i); - app_ref def(m); - if (is_proxy(a, def)) { B.insert(def.get()); } - B.insert (a); - } - - proof_ref pr(m); - pr = get_proof (); - - if (!m_new_unsat_core) { - // old code - farkas_learner learner_old; - learner_old.set_split_literals(m_split_literals); - - learner_old.get_lemmas (pr, B, core); - elim_proxies (core); - simplify_bounds (core); // XXX potentially redundant - } else { - // new code - unsat_core_learner learner(m); - - if (m_farkas_optimized) { - if (true) // TODO: proper options - { - unsat_core_plugin_farkas_lemma_optimized* plugin_farkas_lemma_optimized = alloc(unsat_core_plugin_farkas_lemma_optimized, learner,m); - learner.register_plugin(plugin_farkas_lemma_optimized); - } - else - { - unsat_core_plugin_farkas_lemma_bounded* plugin_farkas_lemma_bounded = alloc(unsat_core_plugin_farkas_lemma_bounded, learner,m); - learner.register_plugin(plugin_farkas_lemma_bounded); - } - - } else { - unsat_core_plugin_farkas_lemma* plugin_farkas_lemma = alloc(unsat_core_plugin_farkas_lemma, learner, m_split_literals, m_farkas_a_const); - learner.register_plugin(plugin_farkas_lemma); - } - - if (m_minimize_unsat_core) { - unsat_core_plugin_min_cut* plugin_min_cut = alloc(unsat_core_plugin_min_cut, learner, m); - learner.register_plugin(plugin_min_cut); - } else { - unsat_core_plugin_lemma* plugin_lemma = alloc(unsat_core_plugin_lemma, learner); - learner.register_plugin(plugin_lemma); - } - - learner.compute_unsat_core(pr, B, core); - - elim_proxies (core); - simplify_bounds (core); // XXX potentially redundant - -// // debug -// expr_ref_vector core2(m); -// unsat_core_learner learner2(m); -// -// unsat_core_plugin_farkas_lemma* plugin_farkas_lemma2 = alloc(unsat_core_plugin_farkas_lemma, learner2, m_split_literals); -// learner2.register_plugin(plugin_farkas_lemma2); -// unsat_core_plugin_lemma* plugin_lemma2 = alloc(unsat_core_plugin_lemma, learner2); -// learner2.register_plugin(plugin_lemma2); -// learner2.compute_unsat_core(pr, B, core2); -// -// elim_proxies (core2); -// simplify_bounds (core2); -// -// IF_VERBOSE(2, -// verbose_stream () << "Itp Core:\n" -// << mk_pp (mk_and (core), m) << "\n";); -// IF_VERBOSE(2, -// verbose_stream () << "Itp Core2:\n" -// << mk_pp (mk_and (core2), m) << "\n";); - //SASSERT(mk_and (core) == mk_and (core2)); - } - - IF_VERBOSE(2, - verbose_stream () << "Itp Core:\n" - << mk_pp (mk_and (core), m) << "\n";); - -} - -void itp_solver::refresh () -{ - // only refresh in non-pushed state - SASSERT (m_defs.size () == 0); - expr_ref_vector assertions (m); - for (unsigned i = 0, e = m_solver.get_num_assertions(); i < e; ++i) { - expr* a = m_solver.get_assertion (i); - if (!m_base_defs.is_proxy_def(a)) { assertions.push_back(a); } - - } - m_base_defs.reset (); - NOT_IMPLEMENTED_YET (); - // solver interface does not have a reset method. need to introduce it somewhere. - // m_solver.reset (); - for (unsigned i = 0, e = assertions.size (); i < e; ++i) - { m_solver.assert_expr(assertions.get(i)); } -} - -} diff --git a/src/muz/spacer/spacer_itp_solver.h b/src/muz/spacer/spacer_itp_solver.h deleted file mode 100644 index 466e0a2f1..000000000 --- a/src/muz/spacer/spacer_itp_solver.h +++ /dev/null @@ -1,172 +0,0 @@ -/** -Copyright (c) 2017 Arie Gurfinkel - -Module Name: - - spacer_itp_solver.h - -Abstract: - - A solver that produces interpolated unsat cores - -Author: - - Arie Gurfinkel - -Notes: - ---*/ -#ifndef SPACER_ITP_SOLVER_H_ -#define SPACER_ITP_SOLVER_H_ - -#include"solver/solver.h" -#include"ast/expr_substitution.h" -#include"util/stopwatch.h" -namespace spacer { -class itp_solver : public solver { -private: - struct def_manager { - itp_solver &m_parent; - obj_map m_expr2proxy; - obj_map m_proxy2def; - - expr_ref_vector m_defs; - - def_manager(itp_solver &parent) : - m_parent(parent), m_defs(m_parent.m) - {} - - bool is_proxy(app *k, app_ref &v); - app* mk_proxy(expr *v); - void reset(); - bool is_proxy_def(expr *v); - - }; - - friend struct def_manager; - ast_manager &m; - solver &m_solver; - app_ref_vector m_proxies; - unsigned m_num_proxies; - vector m_defs; - def_manager m_base_defs; - expr_ref_vector m_assumptions; - unsigned m_first_assumption; - bool m_is_proxied; - - stopwatch m_itp_watch; - - expr_substitution m_elim_proxies_sub; - bool m_split_literals; - bool m_new_unsat_core; - bool m_minimize_unsat_core; - bool m_farkas_optimized; - bool m_farkas_a_const; - - bool is_proxy(expr *e, app_ref &def); - void undo_proxies_in_core(ptr_vector &v); - app* mk_proxy(expr *v); - app* fresh_proxy(); - void elim_proxies(expr_ref_vector &v); -public: - itp_solver(solver &solver, bool new_unsat_core, bool minimize_unsat_core, bool farkas_optimized, bool farkas_a_const, bool split_literals = false) : - m(solver.get_manager()), - m_solver(solver), - m_proxies(m), - m_num_proxies(0), - m_base_defs(*this), - m_assumptions(m), - m_first_assumption(0), - m_is_proxied(false), - m_elim_proxies_sub(m, false, true), - m_split_literals(split_literals), - m_new_unsat_core(new_unsat_core), - m_minimize_unsat_core(minimize_unsat_core), - m_farkas_optimized(farkas_optimized), - m_farkas_a_const(farkas_a_const) - {} - - ~itp_solver() override {} - - /* itp solver specific */ - void get_unsat_core(expr_ref_vector &core) override; - virtual void get_itp_core(expr_ref_vector &core); - void set_split_literals(bool v) {m_split_literals = v;} - bool mk_proxies(expr_ref_vector &v, unsigned from = 0); - void undo_proxies(expr_ref_vector &v); - - void push_bg(expr *e); - void pop_bg(unsigned n); - unsigned get_num_bg(); - - void get_full_unsat_core(ptr_vector &core) - {m_solver.get_unsat_core(core);} - - /* solver interface */ - - solver* translate(ast_manager &m, params_ref const &p) override { return m_solver.translate(m, p);} - void updt_params(params_ref const &p) override { m_solver.updt_params(p);} - void collect_param_descrs(param_descrs &r) override { m_solver.collect_param_descrs(r);} - void set_produce_models(bool f) override { m_solver.set_produce_models(f);} - void assert_expr_core(expr *t) override { m_solver.assert_expr(t);} - void assert_expr_core2(expr *t, expr *a) override { NOT_IMPLEMENTED_YET();} - expr_ref_vector cube(expr_ref_vector&, unsigned) override { return expr_ref_vector(m); } - - void push() override; - void pop(unsigned n) override; - unsigned get_scope_level() const override - {return m_solver.get_scope_level();} - - lbool check_sat(unsigned num_assumptions, expr * const *assumptions) override; - void set_progress_callback(progress_callback *callback) override - {m_solver.set_progress_callback(callback);} - unsigned get_num_assertions() const override - {return m_solver.get_num_assertions();} - expr * get_assertion(unsigned idx) const override - {return m_solver.get_assertion(idx);} - unsigned get_num_assumptions() const override - {return m_solver.get_num_assumptions();} - expr * get_assumption(unsigned idx) const override - {return m_solver.get_assumption(idx);} - std::ostream &display(std::ostream &out, unsigned n, expr* const* es) const override - { return m_solver.display(out, n, es); } - - /* check_sat_result interface */ - - void collect_statistics(statistics &st) const override ; - virtual void reset_statistics(); - - void get_unsat_core(ptr_vector &r) override; - void get_model_core(model_ref &m) override {m_solver.get_model(m);} - proof *get_proof() override {return m_solver.get_proof();} - std::string reason_unknown() const override - {return m_solver.reason_unknown();} - void set_reason_unknown(char const* msg) override - {m_solver.set_reason_unknown(msg);} - void get_labels(svector &r) override - {m_solver.get_labels(r);} - ast_manager &get_manager() const override {return m;} - - virtual void refresh(); - - class scoped_mk_proxy { - itp_solver &m_s; - expr_ref_vector &m_v; - public: - scoped_mk_proxy(itp_solver &s, expr_ref_vector &v) : m_s(s), m_v(v) - {m_s.mk_proxies(m_v);} - ~scoped_mk_proxy() - {m_s.undo_proxies(m_v);} - }; - - class scoped_bg { - itp_solver &m_s; - unsigned m_bg_sz; - public: - scoped_bg(itp_solver &s) : m_s(s), m_bg_sz(m_s.get_num_bg()) {} - ~scoped_bg() - {if (m_s.get_num_bg() > m_bg_sz) { m_s.pop_bg(m_s.get_num_bg() - m_bg_sz); }} - }; -}; -} -#endif diff --git a/src/muz/spacer/spacer_iuc_proof.cpp b/src/muz/spacer/spacer_iuc_proof.cpp new file mode 100644 index 000000000..b6a522b76 --- /dev/null +++ b/src/muz/spacer/spacer_iuc_proof.cpp @@ -0,0 +1,280 @@ +#include +#include "ast/ast_pp_dot.h" + +#include "muz/spacer/spacer_iuc_proof.h" +#include "ast/for_each_expr.h" +#include "ast/array_decl_plugin.h" +#include "ast/proofs/proof_utils.h" +#include "muz/spacer/spacer_proof_utils.h" +#include "muz/spacer/spacer_util.h" +namespace spacer { + +/* + * ==================================== + * init + * ==================================== + */ +iuc_proof::iuc_proof(ast_manager& m, proof* pr, const expr_set& core_lits) : + m(m), m_pr(pr,m) { + for (auto lit : core_lits) m_core_lits.insert(lit); + // init A-marks and B-marks + collect_core_symbols(); + compute_marks(); +} + +iuc_proof::iuc_proof(ast_manager& m, proof* pr, const expr_ref_vector& core_lits) : + m(m), m_pr(pr,m) { + for (auto lit : core_lits) m_core_lits.insert(lit); + // init A-marks and B-marks + collect_core_symbols(); + compute_marks(); +} +/* + * ==================================== + * methods for computing symbol colors + * ==================================== + */ +class collect_pure_proc { + func_decl_set& m_symbs; +public: + collect_pure_proc(func_decl_set& s):m_symbs(s) {} + + void operator()(app* a) { + if (a->get_family_id() == null_family_id) { + m_symbs.insert(a->get_decl()); + } + } + void operator()(var*) {} + void operator()(quantifier*) {} +}; + +void iuc_proof::collect_core_symbols() +{ + expr_mark visited; + collect_pure_proc proc(m_core_symbols); + for (auto lit : m_core_lits) + for_each_expr(proc, visited, lit); +} + +class is_pure_expr_proc { + func_decl_set const& m_symbs; + array_util m_au; +public: + struct non_pure {}; + + is_pure_expr_proc(func_decl_set const& s, ast_manager& m): + m_symbs(s), + m_au (m) + {} + + void operator()(app* a) { + if (a->get_family_id() == null_family_id) { + if (!m_symbs.contains(a->get_decl())) { + throw non_pure(); + } + } + else if (a->get_family_id () == m_au.get_family_id () && + a->is_app_of (a->get_family_id (), OP_ARRAY_EXT)) { + throw non_pure(); + } + } + void operator()(var*) {} + void operator()(quantifier*) {} +}; + +bool iuc_proof::is_core_pure(expr* e) const +{ + is_pure_expr_proc proc(m_core_symbols, m); + try { + for_each_expr(proc, e); + } + catch (is_pure_expr_proc::non_pure) + {return false;} + + return true; +} + +void iuc_proof::compute_marks() +{ + proof_post_order it(m_pr, m); + while (it.hasNext()) + { + proof* cur = it.next(); + if (m.get_num_parents(cur) == 0) + { + switch(cur->get_decl_kind()) + { + case PR_ASSERTED: + if (m_core_lits.contains(m.get_fact(cur))) + m_b_mark.mark(cur, true); + else + m_a_mark.mark(cur, true); + break; + case PR_HYPOTHESIS: + m_h_mark.mark(cur, true); + break; + default: + break; + } + } + else + { + // collect from parents whether derivation of current node + // contains A-axioms, B-axioms and hypothesis + bool need_to_mark_a = false; + bool need_to_mark_b = false; + bool need_to_mark_h = false; + + for (unsigned i = 0; i < m.get_num_parents(cur); ++i) + { + SASSERT(m.is_proof(cur->get_arg(i))); + proof* premise = to_app(cur->get_arg(i)); + + need_to_mark_a |= m_a_mark.is_marked(premise); + need_to_mark_b |= m_b_mark.is_marked(premise); + need_to_mark_h |= m_h_mark.is_marked(premise); + } + + // if current node is application of a lemma, then all + // active hypotheses are removed + if (cur->get_decl_kind() == PR_LEMMA) need_to_mark_h = false; + + // save results + m_a_mark.mark(cur, need_to_mark_a); + m_b_mark.mark(cur, need_to_mark_b); + m_h_mark.mark(cur, need_to_mark_h); + } + } +} + +/* + * ==================================== + * statistics + * ==================================== + */ + +// debug method +void iuc_proof::dump_farkas_stats() +{ + unsigned fl_total = 0; + unsigned fl_lowcut = 0; + + proof_post_order it(m_pr, m); + while (it.hasNext()) + { + proof* cur = it.next(); + + // if node is theory lemma + if (is_farkas_lemma(m, cur)) + { + fl_total++; + + // check whether farkas lemma is to be interpolated (could + // potentially miss farkas lemmas, which are interpolated, + // because we potentially don't want to use the lowest + // cut) + bool has_blue_nonred_parent = false; + for (unsigned i = 0; i < m.get_num_parents(cur); ++i) { + proof* premise = to_app(cur->get_arg(i)); + if (!is_a_marked(premise) && is_b_marked(premise)) { + has_blue_nonred_parent = true; + break; + } + } + + if (has_blue_nonred_parent && is_a_marked(cur)) + { + SASSERT(is_b_marked(cur)); + fl_lowcut++; + } + } + } + + IF_VERBOSE(1, verbose_stream() + << "\n total farkas lemmas " << fl_total + << " farkas lemmas in lowest cut " << fl_lowcut << "\n";); +} + +void iuc_proof::display_dot(std::ostream& out) { + out << "digraph proof { \n"; + + std::unordered_map ids; + unsigned last_id = 0; + + proof_post_order it(m_pr, m); + while (it.hasNext()) + { + proof* curr = it.next(); + + SASSERT(ids.count(curr->get_id()) == 0); + ids.insert(std::make_pair(curr->get_id(), last_id)); + + std::string color = "white"; + if (this->is_a_marked(curr) && !this->is_b_marked(curr)) + color = "red"; + else if (!this->is_a_marked(curr) && this->is_b_marked(curr)) + color = "blue"; + else if (this->is_a_marked(curr) && this->is_b_marked(curr) ) + color = "purple"; + + // compute node label + std::ostringstream label_ostream; + label_ostream << mk_epp(m.get_fact(curr), m) << "\n"; + std::string label = escape_dot(label_ostream.str()); + + // compute edge-label + std::string edge_label = ""; + if (m.get_num_parents(curr) == 0) { + switch (curr->get_decl_kind()) + { + case PR_ASSERTED: + edge_label = "asserted:"; + break; + case PR_HYPOTHESIS: + edge_label = "hyp:"; + color = "grey"; + break; + case PR_TH_LEMMA: + if (is_farkas_lemma(m, curr)) + edge_label = "th_axiom(farkas):"; + else if (is_arith_lemma(m, curr)) + edge_label = "th_axiom(arith):"; + else + edge_label = "th_axiom:"; + break; + default: + edge_label = "unknown axiom:"; + } + } + else { + if (curr->get_decl_kind() == PR_LEMMA) + edge_label = "lemma:"; + else if (curr->get_decl_kind() == PR_TH_LEMMA) { + if (is_farkas_lemma(m, curr)) + edge_label = "th_lemma(farkas):"; + else if (is_arith_lemma(m, curr)) + edge_label = "th_lemma(arith):"; + else + edge_label = "th_lemma(other):"; + } + } + + // generate entry for node in dot-file + out << "node_" << last_id << " " << "[" + << "shape=box,style=\"filled\"," + << "label=\"" << edge_label << " " << label << "\", " + << "fillcolor=\"" << color << "\"" << "]\n"; + + // add entry for each edge to that node + for (unsigned i = m.get_num_parents(curr); i > 0 ; --i) + { + proof* premise = to_app(curr->get_arg(i-1)); + unsigned pid = ids.at(premise->get_id()); + out << "node_" << pid << " -> " << "node_" << last_id << ";\n"; + } + + ++last_id; + } + out << "\n}" << std::endl; +} +} diff --git a/src/muz/spacer/spacer_iuc_proof.h b/src/muz/spacer/spacer_iuc_proof.h new file mode 100644 index 000000000..a3044ca53 --- /dev/null +++ b/src/muz/spacer/spacer_iuc_proof.h @@ -0,0 +1,67 @@ +#ifndef IUC_PROOF_H_ +#define IUC_PROOF_H_ + +#include +#include "ast/ast.h" + +namespace spacer { +typedef obj_hashtable expr_set; +typedef obj_hashtable func_decl_set; + +/* + * An iuc_proof is a proof together with information of the + * coloring of the axioms. + */ +class iuc_proof +{ +public: + + // Constructs an iuc_proof given an ast_manager, a proof, and a set of + // literals core_lits that might be included in the unsat core + iuc_proof(ast_manager& m, proof* pr, const expr_set& core_lits); + iuc_proof(ast_manager& m, proof* pr, const expr_ref_vector &core_lits); + + // returns the proof object + proof* get() {return m_pr.get();} + + // returns true if all uninterpreted symbols of e are from the core literals + // requires that m_core_symbols has already been computed + bool is_core_pure(expr* e) const; + + bool is_a_marked(proof* p) {return m_a_mark.is_marked(p);} + bool is_b_marked(proof* p) {return m_b_mark.is_marked(p);} + bool is_h_marked(proof* p) {return m_h_mark.is_marked(p);} + + bool is_b_pure (proof *p) { + return !is_h_marked (p) && is_core_pure(m.get_fact (p)); + } + + void display_dot(std::ostream &out); + // debug method + void dump_farkas_stats(); +private: + ast_manager& m; + proof_ref m_pr; + + ast_mark m_a_mark; + ast_mark m_b_mark; + ast_mark m_h_mark; + + // -- literals that are part of the core + expr_set m_core_lits; + + // symbols that occur in any literals in the core + func_decl_set m_core_symbols; + + // collect symbols occurring in B (the core) + void collect_core_symbols(); + + // compute for each formula of the proof whether it derives + // from A or from B + void compute_marks(); +}; + + +} + +#endif /* IUC_PROOF_H_ */ diff --git a/src/muz/spacer/spacer_iuc_solver.cpp b/src/muz/spacer/spacer_iuc_solver.cpp new file mode 100644 index 000000000..a68db4c0a --- /dev/null +++ b/src/muz/spacer/spacer_iuc_solver.cpp @@ -0,0 +1,435 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_iuc_solver.cpp + +Abstract: + + A solver that produces interpolated unsat cores (IUCs) + +Author: + + Arie Gurfinkel + +Notes: + +--*/ +#include"muz/spacer/spacer_iuc_solver.h" +#include"ast/ast.h" +#include"muz/spacer/spacer_util.h" +#include"ast/proofs/proof_utils.h" +#include"muz/spacer/spacer_farkas_learner.h" +#include"ast/rewriter/expr_replacer.h" +#include"muz/spacer/spacer_unsat_core_learner.h" +#include"muz/spacer/spacer_unsat_core_plugin.h" +#include "muz/spacer/spacer_iuc_proof.h" + +namespace spacer { +void iuc_solver::push () +{ + m_defs.push_back (def_manager (*this)); + m_solver.push (); +} + +void iuc_solver::pop (unsigned n) +{ + m_solver.pop (n); + unsigned lvl = m_defs.size (); + SASSERT (n <= lvl); + unsigned new_lvl = lvl-n; + while (m_defs.size() > new_lvl) { + m_num_proxies -= m_defs.back ().m_defs.size (); + m_defs.pop_back (); + } +} + +app* iuc_solver::fresh_proxy () +{ + if (m_num_proxies == m_proxies.size()) { + std::stringstream name; + name << "spacer_proxy!" << m_proxies.size (); + app_ref res(m); + res = m.mk_const (symbol (name.str ().c_str ()), + m.mk_bool_sort ()); + m_proxies.push_back (res); + + // -- add the new proxy to proxy eliminator + proof_ref pr(m); + pr = m.mk_asserted (m.mk_true ()); + m_elim_proxies_sub.insert (res, m.mk_true (), pr); + + } + return m_proxies.get (m_num_proxies++); +} + +app* iuc_solver::mk_proxy (expr *v) +{ + { + expr *e = v; + m.is_not (v, e); + if (is_uninterp_const(e)) { return to_app(v); } + } + + def_manager &def = m_defs.size () > 0 ? m_defs.back () : m_base_defs; + return def.mk_proxy (v); +} + +bool iuc_solver::mk_proxies (expr_ref_vector &v, unsigned from) +{ + bool dirty = false; + for (unsigned i = from, sz = v.size(); i < sz; ++i) { + app *p = mk_proxy (v.get (i)); + dirty |= (v.get (i) != p); + v[i] = p; + } + return dirty; +} + +void iuc_solver::push_bg (expr *e) +{ + if (m_assumptions.size () > m_first_assumption) + { m_assumptions.shrink(m_first_assumption); } + m_assumptions.push_back (e); + m_first_assumption = m_assumptions.size (); +} + +void iuc_solver::pop_bg (unsigned n) +{ + if (n == 0) { return; } + + if (m_assumptions.size () > m_first_assumption) { + m_assumptions.shrink(m_first_assumption); + } + m_first_assumption = m_first_assumption > n ? m_first_assumption - n : 0; + m_assumptions.shrink (m_first_assumption); +} + +unsigned iuc_solver::get_num_bg () {return m_first_assumption;} + +lbool iuc_solver::check_sat (unsigned num_assumptions, expr * const *assumptions) +{ + // -- remove any old assumptions + m_assumptions.shrink(m_first_assumption); + + // -- replace theory literals in background assumptions with proxies + mk_proxies (m_assumptions); + // -- in case mk_proxies added new literals, they are all background + m_first_assumption = m_assumptions.size (); + + m_assumptions.append (num_assumptions, assumptions); + m_is_proxied = mk_proxies (m_assumptions, m_first_assumption); + + return set_status (m_solver.check_sat (m_assumptions)); +} + +lbool iuc_solver::check_sat_cc(const expr_ref_vector &cube, + vector const & clauses) { + if (clauses.empty()) + return check_sat(cube.size(), cube.c_ptr()); + + // -- remove any old assumptions + m_assumptions.shrink(m_first_assumption); + + // -- replace theory literals in background assumptions with proxies + mk_proxies(m_assumptions); + // -- in case mk_proxies added new literals, they are all background + m_first_assumption = m_assumptions.size(); + + m_assumptions.append(cube); + m_is_proxied = mk_proxies(m_assumptions, m_first_assumption); + + return set_status (m_solver.check_sat_cc(m_assumptions, clauses)); +} + + +app* iuc_solver::def_manager::mk_proxy (expr *v) +{ + app* r; + if (m_expr2proxy.find(v, r)) + return r; + + ast_manager &m = m_parent.m; + app* proxy = m_parent.fresh_proxy (); + app* def = m.mk_or (m.mk_not (proxy), v); + m_defs.push_back (def); + m_expr2proxy.insert (v, proxy); + m_proxy2def.insert (proxy, def); + + m_parent.assert_expr (def); + return proxy; +} + +bool iuc_solver::def_manager::is_proxy (app *k, app_ref &def) +{ + app *r = nullptr; + bool found = m_proxy2def.find (k, r); + def = r; + return found; +} + +void iuc_solver::def_manager::reset () +{ + m_expr2proxy.reset (); + m_proxy2def.reset (); + m_defs.reset (); +} + +bool iuc_solver::def_manager::is_proxy_def (expr *v) +{ + // XXX This might not be the most robust way to check + return m_defs.contains (v); +} + +bool iuc_solver::is_proxy(expr *e, app_ref &def) +{ + if (!is_uninterp_const(e)) + return false; + + app* a = to_app (e); + + for (int i = m_defs.size (); i-- > 0; ) + if (m_defs[i].is_proxy (a, def)) + return true; + + return m_base_defs.is_proxy (a, def); +} + +void iuc_solver::collect_statistics (statistics &st) const +{ + m_solver.collect_statistics (st); + st.update ("time.iuc_solver.get_iuc", m_iuc_sw.get_seconds()); + st.update ("time.iuc_solver.get_iuc.hyp_reduce1", m_hyp_reduce1_sw.get_seconds()); + st.update ("time.iuc_solver.get_iuc.hyp_reduce2", m_hyp_reduce2_sw.get_seconds()); + st.update ("time.iuc_solver.get_iuc.learn_core", m_learn_core_sw.get_seconds()); + + st.update("iuc_solver.num_proxies", m_proxies.size()); +} + +void iuc_solver::reset_statistics () +{ + m_iuc_sw.reset(); + m_hyp_reduce1_sw.reset(); + m_hyp_reduce2_sw.reset(); + m_learn_core_sw.reset(); +} + +void iuc_solver::get_unsat_core (expr_ref_vector &core) { + m_solver.get_unsat_core (core); + undo_proxies_in_core (core); +} + +void iuc_solver::undo_proxies_in_core (expr_ref_vector &r) +{ + app_ref e(m); + expr_fast_mark1 bg; + for (unsigned i = 0; i < m_first_assumption; ++i) { + bg.mark(m_assumptions.get(i)); + } + + // expand proxies + unsigned j = 0; + for (expr* rr : r) { + // skip background assumptions + if (bg.is_marked(rr)) + continue; + + // -- undo proxies, but only if they were introduced in check_sat + if (m_is_proxied && is_proxy(rr, e)) { + SASSERT (m.is_or (e)); + r[j++] = e->get_arg (1); + } + else { + r[j++] = rr; + } + } + r.shrink (j); +} + +void iuc_solver::undo_proxies (expr_ref_vector &r) +{ + app_ref e(m); + // expand proxies + for (unsigned i = 0, sz = r.size (); i < sz; ++i) + if (is_proxy(r.get(i), e)) { + SASSERT (m.is_or (e)); + r[i] = e->get_arg (1); + } +} + +void iuc_solver::elim_proxies (expr_ref_vector &v) +{ + expr_ref f = mk_and (v); + scoped_ptr rep = mk_expr_simp_replacer (m); + rep->set_substitution (&m_elim_proxies_sub); + (*rep)(f); + v.reset(); + flatten_and(f, v); +} + +void iuc_solver::get_iuc(expr_ref_vector &core) +{ + scoped_watch _t_ (m_iuc_sw); + + typedef obj_hashtable expr_set; + expr_set core_lits; + for (unsigned i = m_first_assumption, sz = m_assumptions.size(); i < sz; ++i) { + expr *a = m_assumptions.get (i); + app_ref def(m); + if (is_proxy(a, def)) { core_lits.insert(def.get()); } + core_lits.insert (a); + } + + if (m_iuc == 0) { + // ORIGINAL PDR CODE + // AG: deprecated + proof_ref pr(m); + pr = get_proof (); + + farkas_learner learner_old; + learner_old.set_split_literals(m_split_literals); + + learner_old.get_lemmas (pr, core_lits, core); + elim_proxies (core); + simplify_bounds (core); // XXX potentially redundant + } + else { + // NEW IUC + proof_ref res(get_proof(), m); + + // -- old hypothesis reducer while the new one is broken + if (m_old_hyp_reducer) { + scoped_watch _t_ (m_hyp_reduce1_sw); + // AG: deprecated + // pre-process proof in order to get a proof which is + // better suited for unsat-core-extraction + if (m_print_farkas_stats) { + iuc_proof iuc_before(m, res.get(), core_lits); + verbose_stream() << "\nOld reduce_hypotheses. Before:"; + iuc_before.dump_farkas_stats(); + } + + proof_utils::reduce_hypotheses(res); + proof_utils::permute_unit_resolution(res); + + if (m_print_farkas_stats) { + iuc_proof iuc_after(m, res.get(), core_lits); + verbose_stream() << "Old reduce_hypothesis. After:"; + iuc_after.dump_farkas_stats(); + } + } + // -- new hypothesis reducer + else + { + scoped_watch _t_ (m_hyp_reduce2_sw); + + // pre-process proof for better iuc extraction + if (m_print_farkas_stats) { + iuc_proof iuc_before(m, res.get(), core_lits); + verbose_stream() << "\n New hypothesis_reducer. Before:"; + iuc_before.dump_farkas_stats(); + } + + proof_ref pr1(m); + { + scoped_watch _t_ (m_hyp_reduce1_sw); + theory_axiom_reducer ta_reducer(m); + pr1 = ta_reducer.reduce (res.get()); + } + + proof_ref pr2(m); + { + scoped_watch _t_ (m_hyp_reduce2_sw); + hypothesis_reducer hyp_reducer(m); + pr2 = hyp_reducer.reduce(pr1); + } + + res = pr2; + + if (m_print_farkas_stats) { + iuc_proof iuc_after(m, res.get(), core_lits); + verbose_stream() << "New hypothesis_reducer. After:"; + iuc_after.dump_farkas_stats(); + } + } + + iuc_proof iuc_pf(m, res, core_lits); + + unsat_core_learner learner(m, iuc_pf); + + unsat_core_plugin* plugin; + // -- register iuc plugins + switch (m_iuc_arith) { + case 0: + case 1: + plugin = + alloc(unsat_core_plugin_farkas_lemma, + learner, m_split_literals, + (m_iuc_arith == 1) /* use constants from A */); + learner.register_plugin(plugin); + break; + case 2: + SASSERT(false && "Broken"); + plugin = alloc(unsat_core_plugin_farkas_lemma_optimized, learner, m); + learner.register_plugin(plugin); + break; + case 3: + plugin = alloc(unsat_core_plugin_farkas_lemma_bounded, learner, m); + learner.register_plugin(plugin); + break; + default: + UNREACHABLE(); + break; + } + + switch (m_iuc) { + case 1: + // -- iuc based on the lowest cut in the proof + plugin = alloc(unsat_core_plugin_lemma, learner); + learner.register_plugin(plugin); + break; + case 2: + // -- iuc based on the smallest cut in the proof + plugin = alloc(unsat_core_plugin_min_cut, learner, m); + learner.register_plugin(plugin); + break; + default: + UNREACHABLE(); + break; + } + + { + scoped_watch _t_ (m_learn_core_sw); + // compute interpolating unsat core + learner.compute_unsat_core(core); + } + + elim_proxies (core); + // AG: this should be taken care of by minimizing the iuc cut + simplify_bounds (core); + } + + IF_VERBOSE(2, + verbose_stream () << "IUC Core:\n" << core << "\n";); +} + +void iuc_solver::refresh () +{ + // only refresh in non-pushed state + SASSERT (m_defs.empty()); + expr_ref_vector assertions (m); + for (unsigned i = 0, e = m_solver.get_num_assertions(); i < e; ++i) { + expr* a = m_solver.get_assertion (i); + if (!m_base_defs.is_proxy_def(a)) { assertions.push_back(a); } + + } + m_base_defs.reset (); + NOT_IMPLEMENTED_YET (); + // solver interface does not have a reset method. need to introduce it somewhere. + // m_solver.reset (); + for (unsigned i = 0, e = assertions.size (); i < e; ++i) + { m_solver.assert_expr(assertions.get(i)); } +} + +} diff --git a/src/muz/spacer/spacer_iuc_solver.h b/src/muz/spacer/spacer_iuc_solver.h new file mode 100644 index 000000000..c0a0072ed --- /dev/null +++ b/src/muz/spacer/spacer_iuc_solver.h @@ -0,0 +1,181 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_iuc_solver.h + +Abstract: + + A solver that produces interpolated unsat cores + +Author: + + Arie Gurfinkel + +Notes: + +--*/ +#ifndef SPACER_IUC_SOLVER_H_ +#define SPACER_IUC_SOLVER_H_ + +#include"solver/solver.h" +#include"ast/expr_substitution.h" +#include"util/stopwatch.h" +namespace spacer { +class iuc_solver : public solver { +private: + struct def_manager { + iuc_solver & m_parent; + expr_ref_vector m_defs; + obj_map m_expr2proxy; + obj_map m_proxy2def; + + def_manager(iuc_solver &parent) : + m_parent(parent), m_defs(m_parent.m) + {} + + bool is_proxy(app *k, app_ref &v); + app* mk_proxy(expr *v); + void reset(); + bool is_proxy_def(expr *v); + + }; + + friend struct def_manager; + ast_manager& m; + solver& m_solver; + app_ref_vector m_proxies; + unsigned m_num_proxies; + vector m_defs; + def_manager m_base_defs; + expr_ref_vector m_assumptions; + unsigned m_first_assumption; + bool m_is_proxied; + + stopwatch m_iuc_sw; + stopwatch m_hyp_reduce1_sw; + stopwatch m_hyp_reduce2_sw; + stopwatch m_learn_core_sw; + + expr_substitution m_elim_proxies_sub; + bool m_split_literals; + unsigned m_iuc; + unsigned m_iuc_arith; + bool m_print_farkas_stats; + bool m_old_hyp_reducer; + bool is_proxy(expr *e, app_ref &def); + void undo_proxies_in_core(expr_ref_vector &v); + app* mk_proxy(expr *v); + app* fresh_proxy(); + void elim_proxies(expr_ref_vector &v); +public: + iuc_solver(solver &solver, unsigned iuc, unsigned iuc_arith, + bool print_farkas_stats, bool old_hyp_reducer, + bool split_literals = false) : + m(solver.get_manager()), + m_solver(solver), + m_proxies(m), + m_num_proxies(0), + m_base_defs(*this), + m_assumptions(m), + m_first_assumption(0), + m_is_proxied(false), + m_elim_proxies_sub(m, false, true), + m_split_literals(split_literals), + m_iuc(iuc), + m_iuc_arith(iuc_arith), + m_print_farkas_stats(print_farkas_stats), + m_old_hyp_reducer(old_hyp_reducer) + {} + + ~iuc_solver() override {} + + /* iuc solver specific */ + virtual void get_iuc(expr_ref_vector &core); + void set_split_literals(bool v) { m_split_literals = v; } + bool mk_proxies(expr_ref_vector &v, unsigned from = 0); + void undo_proxies(expr_ref_vector &v); + + void push_bg(expr *e); + void pop_bg(unsigned n); + unsigned get_num_bg(); + + void get_full_unsat_core(ptr_vector &core) { + expr_ref_vector _core(m); + m_solver.get_unsat_core(_core); + core.append(_core.size(), _core.c_ptr()); + } + + /* solver interface */ + + solver* translate(ast_manager &m, params_ref const &p) override { + return m_solver.translate(m, p); + } + void updt_params(params_ref const &p) override { m_solver.updt_params(p); } + void reset_params(params_ref const &p) override { m_solver.reset_params(p); } + const params_ref &get_params() const override { return m_solver.get_params(); } + void push_params() override { m_solver.push_params(); } + void pop_params() override { m_solver.pop_params(); } + void collect_param_descrs(param_descrs &r) override { m_solver.collect_param_descrs(r); } + void set_produce_models(bool f) override { m_solver.set_produce_models(f); } + void assert_expr_core(expr *t) override { m_solver.assert_expr(t); } + void assert_expr_core2(expr *t, expr *a) override { NOT_IMPLEMENTED_YET(); } + expr_ref_vector cube(expr_ref_vector&, unsigned) override { return expr_ref_vector(m); } + + void push() override; + void pop(unsigned n) override; + unsigned get_scope_level() const override { return m_solver.get_scope_level(); } + + lbool check_sat(unsigned num_assumptions, expr * const *assumptions) override; + lbool check_sat_cc(const expr_ref_vector &cube, vector const & clauses) override; + void set_progress_callback(progress_callback *callback) override { + m_solver.set_progress_callback(callback); + } + unsigned get_num_assertions() const override { return m_solver.get_num_assertions(); } + expr * get_assertion(unsigned idx) const override { return m_solver.get_assertion(idx); } + unsigned get_num_assumptions() const override { return m_solver.get_num_assumptions(); } + expr * get_assumption(unsigned idx) const override { return m_solver.get_assumption(idx); } + std::ostream &display(std::ostream &out, unsigned n, expr* const* es) const override { + return m_solver.display(out, n, es); + } + + /* check_sat_result interface */ + + void collect_statistics(statistics &st) const override ; + virtual void reset_statistics(); + + void get_unsat_core(expr_ref_vector &r) override; + void get_model_core(model_ref &m) override {m_solver.get_model(m);} + proof *get_proof() override {return m_solver.get_proof();} + std::string reason_unknown() const override { return m_solver.reason_unknown(); } + void set_reason_unknown(char const* msg) override { m_solver.set_reason_unknown(msg); } + void get_labels(svector &r) override { m_solver.get_labels(r); } + ast_manager& get_manager() const override { return m; } + + virtual void refresh(); + + class scoped_mk_proxy { + iuc_solver &m_s; + expr_ref_vector &m_v; + public: + scoped_mk_proxy(iuc_solver &s, expr_ref_vector &v) : m_s(s), m_v(v) { + m_s.mk_proxies(m_v); + } + ~scoped_mk_proxy() { m_s.undo_proxies(m_v); } + }; + + class scoped_bg { + iuc_solver &m_s; + unsigned m_bg_sz; + public: + scoped_bg(iuc_solver &s) : m_s(s), m_bg_sz(m_s.get_num_bg()) {} + ~scoped_bg() { + if (m_s.get_num_bg() > m_bg_sz) { + m_s.pop_bg(m_s.get_num_bg() - m_bg_sz); + } + } + }; +}; +} +#endif diff --git a/src/muz/spacer/spacer_json.cpp b/src/muz/spacer/spacer_json.cpp new file mode 100644 index 000000000..a99a8f298 --- /dev/null +++ b/src/muz/spacer/spacer_json.cpp @@ -0,0 +1,191 @@ +/**++ +Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti + +Module Name: + + spacer_json.cpp + +Abstract: + + SPACER json marshalling support + +Author: + + Matteo Marescotti + +Notes: + +--*/ + +#include +#include "spacer_context.h" +#include "spacer_json.h" +#include "spacer_util.h" + +namespace spacer { + +static std::ostream &json_marshal(std::ostream &out, ast *t, ast_manager &m) { + + mk_epp pp = mk_epp(t, m); + std::ostringstream ss; + ss << pp; + out << "\""; + for (auto &c:ss.str()) { + switch (c) { + case '"': + out << "\\\""; + break; + case '\\': + out << "\\\\"; + break; + case '\b': + out << "\\b"; + break; + case '\f': + out << "\\f"; + break; + case '\n': + out << "\\n"; + break; + case '\r': + out << "\\r"; + break; + case '\t': + out << "\\t"; + break; + default: + if ('\x00' <= c && c <= '\x1f') { + out << "\\u" + << std::hex << std::setw(4) << std::setfill('0') << (int) c; + } else { + out << c; + } + } + } + out << "\""; + return out; +} + +static std::ostream &json_marshal(std::ostream &out, lemma *l) { + out << "{" + << R"("init_level":")" << l->init_level() + << R"(", "level":")" << l->level() + << R"(", "expr":)"; + json_marshal(out, l->get_expr(), l->get_ast_manager()); + out << "}"; + return out; +} + +static std::ostream &json_marshal(std::ostream &out, const lemma_ref_vector &lemmas) { + + std::ostringstream ls; + for (auto l:lemmas) { + ls << ((unsigned)ls.tellp() == 0 ? "" : ","); + json_marshal(ls, l); + } + out << "[" << ls.str() << "]"; + return out; + } + + +void json_marshaller::register_lemma(lemma *l) { + if (l->has_pob()) { + m_relations[&*l->get_pob()][l->get_pob()->depth()].push_back(l); + } +} + +void json_marshaller::register_pob(pob *p) { + m_relations[p]; +} + +void json_marshaller::marshal_lemmas_old(std::ostream &out) const { + unsigned pob_id = 0; + for (auto &pob_map:m_relations) { + std::ostringstream pob_lemmas; + for (auto &depth_lemmas : pob_map.second) { + pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",") + << "\"" << depth_lemmas.first << "\":"; + json_marshal(pob_lemmas, depth_lemmas.second); + } + if (pob_lemmas.tellp()) { + out << ((unsigned)out.tellp() == 0 ? "" : ",\n"); + out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}"; + } + pob_id++; + } +} +void json_marshaller::marshal_lemmas_new(std::ostream &out) const { + unsigned pob_id = 0; + for (auto &pob_map:m_relations) { + std::ostringstream pob_lemmas; + pob *n = pob_map.first; + unsigned i = 0; + for (auto *l : n->lemmas()) { + pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",") + << "\"" << i++ << "\":"; + lemma_ref_vector lemmas_vec; + lemmas_vec.push_back(l); + json_marshal(pob_lemmas, lemmas_vec); + } + + if (pob_lemmas.tellp()) { + out << ((unsigned)out.tellp() == 0 ? "" : ",\n"); + out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}"; + } + pob_id++; + } +} + +std::ostream &json_marshaller::marshal(std::ostream &out) const { + std::ostringstream nodes; + std::ostringstream edges; + std::ostringstream lemmas; + + if (m_old_style) + marshal_lemmas_old(lemmas); + else + marshal_lemmas_new(lemmas); + + unsigned pob_id = 0; + unsigned depth = 0; + while (true) { + double root_expand_time = m_ctx->get_root().get_expand_time(depth); + bool a = false; + pob_id = 0; + for (auto &pob_map:m_relations) { + pob *n = pob_map.first; + double expand_time = n->get_expand_time(depth); + if (expand_time > 0) { + a = true; + std::ostringstream pob_expr; + json_marshal(pob_expr, n->post(), n->get_ast_manager()); + + nodes << ((unsigned)nodes.tellp() == 0 ? "" : ",\n") << + "{\"id\":\"" << depth << n << + "\",\"relative_time\":\"" << expand_time / root_expand_time << + "\",\"absolute_time\":\"" << std::setprecision(2) << expand_time << + "\",\"predicate\":\"" << n->pt().head()->get_name() << + "\",\"expr_id\":\"" << n->post()->get_id() << + "\",\"pob_id\":\"" << pob_id << + "\",\"depth\":\"" << depth << + "\",\"expr\":" << pob_expr.str() << "}"; + if (n->parent()) { + edges << ((unsigned)edges.tellp() == 0 ? "" : ",\n") << + "{\"from\":\"" << depth << n->parent() << + "\",\"to\":\"" << depth << n << "\"}"; + } + } + pob_id++; + } + if (!a) { + break; + } + depth++; + } + out << "{\n\"nodes\":[\n" << nodes.str() << "\n],\n"; + out << "\"edges\":[\n" << edges.str() << "\n],\n"; + out << "\"lemmas\":{\n" << lemmas.str() << "\n}\n}\n"; + return out; +} + +} diff --git a/src/muz/spacer/spacer_json.h b/src/muz/spacer/spacer_json.h new file mode 100644 index 000000000..131e72678 --- /dev/null +++ b/src/muz/spacer/spacer_json.h @@ -0,0 +1,61 @@ +/**++ +Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti + +Module Name: + + spacer_json.h + +Abstract: + + SPACER json marshalling support + +Author: + + Matteo Marescotti + +Notes: + +--*/ + +#ifndef Z3_SPACER_JSON_H +#define Z3_SPACER_JSON_H + +#include +#include +#include "util/ref.h" +#include "util/ref_vector.h" + +class ast; + +class ast_manager; + +namespace spacer { + +class lemma; +typedef sref_vector lemma_ref_vector; +class context; +class pob; + + +class json_marshaller { + context *m_ctx; + bool m_old_style; + std::map> m_relations; + + void marshal_lemmas_old(std::ostream &out) const; + void marshal_lemmas_new(std::ostream &out) const; +public: + json_marshaller(context *ctx, bool old_style = false) : + m_ctx(ctx), m_old_style(old_style) {} + + void register_lemma(lemma *l); + + void register_pob(pob *p); + + std::ostream &marshal(std::ostream &out) const; +}; + +} + + +#endif //Z3_SPACER_JSON_H diff --git a/src/muz/spacer/spacer_legacy_frames.cpp b/src/muz/spacer/spacer_legacy_frames.cpp index 49157a085..a21df5038 100644 --- a/src/muz/spacer/spacer_legacy_frames.cpp +++ b/src/muz/spacer/spacer_legacy_frames.cpp @@ -34,7 +34,7 @@ #include "util/luby.h" #include "ast/rewriter/expr_safe_replace.h" #include "ast/expr_abstract.h" -#include "ast/factor_equivs.h" +#include "ast/rewriter/factor_equivs.h" namespace spacer { diff --git a/src/muz/spacer/spacer_legacy_mbp.cpp b/src/muz/spacer/spacer_legacy_mbp.cpp index 9f03e6d2f..76b543f04 100644 --- a/src/muz/spacer/spacer_legacy_mbp.cpp +++ b/src/muz/spacer/spacer_legacy_mbp.cpp @@ -71,10 +71,11 @@ void qe_project(ast_manager& m, app_ref_vector& vars, expr_ref& fml, model_ref& expr_substitution sub(m); proof_ref pr(m.mk_asserted(m.mk_true()), m); expr_ref bval(m); + model::scoped_model_completion _scm(*M, true); for (unsigned i = 0; i < vars.size(); i++) { if (m.is_bool(vars.get(i))) { // obtain the interpretation of the ith var using model completion - VERIFY(M->eval(vars.get(i), bval, true)); + bval = (*M)(vars.get(i)); sub.insert(vars.get(i), bval, pr); } else { arith_vars.push_back(vars.get(i)); @@ -99,14 +100,14 @@ void qe_project(ast_manager& m, app_ref_vector& vars, expr_ref& fml, model_ref& ); { scoped_no_proof _sp(m); - qe::arith_project(*M, arith_vars, fml, map); + spacer_qe::arith_project(*M, arith_vars, fml, map); } SASSERT(arith_vars.empty()); TRACE("spacer", tout << "Projected arith vars:\n" << mk_pp(fml, m) << "\n"; ); } - SASSERT(M->eval(fml, bval, true) && m.is_true(bval)); // M |= fml + SASSERT(M->is_true(fml)); vars.reset(); vars.append(arith_vars); } diff --git a/src/muz/spacer/spacer_legacy_mev.cpp b/src/muz/spacer/spacer_legacy_mev.cpp index fc3eabc56..b62b24b0d 100644 --- a/src/muz/spacer/spacer_legacy_mev.cpp +++ b/src/muz/spacer/spacer_legacy_mev.cpp @@ -138,7 +138,6 @@ void model_evaluator::process_formula(app* e, ptr_vector& todo, ptr_vector case OP_FALSE: break; case OP_EQ: - case OP_IFF: if (args[0] == args[1]) { SASSERT(v); // no-op @@ -462,8 +461,8 @@ void model_evaluator::eval_array_eq(app* e, expr* arg1, expr* arg2) { TRACE("old_spacer", tout << "array equality: " << mk_pp(e, m) << "\n";); expr_ref v1(m), v2(m); - m_model->eval(arg1, v1); - m_model->eval(arg2, v2); + v1 = (*m_model)(arg1); + v2 = (*m_model)(arg2); if (v1 == v2) { set_true(e); return; @@ -511,8 +510,8 @@ void model_evaluator::eval_array_eq(app* e, expr* arg1, expr* arg2) args2.append(store[i].size() - 1, store[i].c_ptr()); s1 = m_array.mk_select(args1.size(), args1.c_ptr()); s2 = m_array.mk_select(args2.size(), args2.c_ptr()); - m_model->eval(s1, w1); - m_model->eval(s2, w2); + w1 = (*m_model)(s1); + w2 = (*m_model)(s2); if (w1 == w2) { continue; } @@ -634,10 +633,6 @@ void model_evaluator::eval_basic(app* e) set_x(e); } break; - case OP_IFF: - VERIFY(m.is_iff(e, arg1, arg2)); - eval_eq(e, arg1, arg2); - break; case OP_XOR: VERIFY(m.is_xor(e, arg1, arg2)); eval_eq(e, arg1, arg2); @@ -734,7 +729,7 @@ void model_evaluator::eval_fmls(ptr_vector const& formulas) eval_basic(curr); } else { expr_ref vl(m); - m_model->eval(curr, vl); + vl = eval(m_model, curr); assign_value(curr, vl); } @@ -803,11 +798,10 @@ expr_ref model_evaluator::eval(const model_ref& model, func_decl* d) return result; } -expr_ref model_evaluator::eval(const model_ref& model, expr* e) -{ - expr_ref result(m); +expr_ref model_evaluator::eval(const model_ref& model, expr* e){ m_model = model.get(); - VERIFY(m_model->eval(e, result, true)); + model::scoped_model_completion _scm(m_model, true); + expr_ref result = (*m_model)(e); if (m_array.is_array(e)) { vector stores; expr_ref_vector args(m); diff --git a/src/muz/spacer/spacer_manager.cpp b/src/muz/spacer/spacer_manager.cpp index ba4ca0da7..856207463 100644 --- a/src/muz/spacer/spacer_manager.cpp +++ b/src/muz/spacer/spacer_manager.cpp @@ -30,6 +30,7 @@ Revision History: #include "model/model_smt2_pp.h" #include "tactic/model_converter.h" +#include "smt/smt_solver.h" namespace spacer { class collect_decls_proc { @@ -168,167 +169,30 @@ void inductive_property::display(datalog::rule_manager& rm, ptr_vector manager::get_state_suffixes() -{ - std::vector res; - res.push_back("_n"); - return res; -} - -manager::manager(unsigned max_num_contexts, ast_manager& manager) : - m(manager), - m_brwr(m), - m_mux(m, get_state_suffixes()), - m_background(m.mk_true(), m), - m_contexts(m, max_num_contexts), - m_contexts2(m, max_num_contexts), - m_contexts3(m, max_num_contexts), - m_next_unique_num(0) -{ -} -void manager::add_new_state(func_decl * s) -{ - SASSERT(s->get_arity() == 0); //we currently don't support non-constant states - decl_vector vect; +manager::manager(ast_manager& manager) : m(manager), m_mux(m) {} - SASSERT(o_index(0) == 1); //we assume this in the number of retrieved symbols - m_mux.create_tuple(s, s->get_arity(), s->get_domain(), s->get_range(), 2, vect); - m_o0_preds.push_back(vect[o_index(0)]); -} - -func_decl * manager::get_o_pred(func_decl* s, unsigned idx) -{ - func_decl * res = m_mux.try_get_by_prefix(s, o_index(idx)); - if (res) { return res; } - add_new_state(s); - res = m_mux.try_get_by_prefix(s, o_index(idx)); +func_decl * manager::get_o_pred(func_decl* s, unsigned idx) { + func_decl * res = m_mux.find_by_decl(s, o_index(idx)); + if (!res) { + m_mux.register_decl(s); + res = m_mux.find_by_decl(s, o_index(idx)); + } SASSERT(res); return res; } -func_decl * manager::get_n_pred(func_decl* s) -{ - func_decl * res = m_mux.try_get_by_prefix(s, n_index()); - if (res) { return res; } - add_new_state(s); - res = m_mux.try_get_by_prefix(s, n_index()); +func_decl * manager::get_n_pred(func_decl* s) { + func_decl * res = m_mux.find_by_decl(s, n_index()); + if (!res) { + m_mux.register_decl(s); + res = m_mux.find_by_decl(s, n_index()); + } SASSERT(res); return res; } -void manager::mk_model_into_cube(const expr_ref_vector & mdl, expr_ref & res) -{ - m_brwr.mk_and(mdl.size(), mdl.c_ptr(), res); -} - -void manager::mk_core_into_cube(const expr_ref_vector & core, expr_ref & res) -{ - m_brwr.mk_and(core.size(), core.c_ptr(), res); -} - -void manager::mk_cube_into_lemma(expr * cube, expr_ref & res) -{ - m_brwr.mk_not(cube, res); -} - -void manager::mk_lemma_into_cube(expr * lemma, expr_ref & res) -{ - m_brwr.mk_not(lemma, res); -} - -expr_ref manager::mk_and(unsigned sz, expr* const* exprs) -{ - expr_ref result(m); - m_brwr.mk_and(sz, exprs, result); - return result; -} - -expr_ref manager::mk_or(unsigned sz, expr* const* exprs) -{ - expr_ref result(m); - m_brwr.mk_or(sz, exprs, result); - return result; -} - -expr_ref manager::mk_not_and(expr_ref_vector const& conjs) -{ - expr_ref result(m), e(m); - expr_ref_vector es(conjs); - flatten_and(es); - for (unsigned i = 0; i < es.size(); ++i) { - m_brwr.mk_not(es[i].get(), e); - es[i] = e; - } - m_brwr.mk_or(es.size(), es.c_ptr(), result); - return result; -} - -void manager::get_or(expr* e, expr_ref_vector& result) -{ - result.push_back(e); - for (unsigned i = 0; i < result.size();) { - e = result[i].get(); - if (m.is_or(e)) { - result.append(to_app(e)->get_num_args(), to_app(e)->get_args()); - result[i] = result.back(); - result.pop_back(); - } else { - ++i; - } - } -} - -bool manager::try_get_state_and_value_from_atom(expr * atom0, app *& state, app_ref& value) -{ - if (!is_app(atom0)) { - return false; - } - app * atom = to_app(atom0); - expr * arg1; - expr * arg2; - app * candidate_state; - app_ref candidate_value(m); - if (m.is_not(atom, arg1)) { - if (!is_app(arg1)) { - return false; - } - candidate_state = to_app(arg1); - candidate_value = m.mk_false(); - } else if (m.is_eq(atom, arg1, arg2)) { - if (!is_app(arg1) || !is_app(arg2)) { - return false; - } - if (!m_mux.is_muxed(to_app(arg1)->get_decl())) { - std::swap(arg1, arg2); - } - candidate_state = to_app(arg1); - candidate_value = to_app(arg2); - } else { - candidate_state = atom; - candidate_value = m.mk_true(); - } - if (!m_mux.is_muxed(candidate_state->get_decl())) { - return false; - } - state = candidate_state; - value = candidate_value; - return true; -} - -bool manager::try_get_state_decl_from_atom(expr * atom, func_decl *& state) -{ - app_ref dummy_value_holder(m); - app * s; - if (try_get_state_and_value_from_atom(atom, s, dummy_value_holder)) { - state = s->get_decl(); - return true; - } else { - return false; - } -} - /** * Create a new skolem constant */ @@ -340,22 +204,27 @@ app* mk_zk_const(ast_manager &m, unsigned idx, sort *s) { namespace find_zk_const_ns { struct proc { + int m_max; app_ref_vector &m_out; - proc (app_ref_vector &out) : m_out(out) {} + proc (app_ref_vector &out) : m_max(-1), m_out(out) {} void operator() (var const * n) const {} - void operator() (app *n) const { - if (is_uninterp_const(n) && - n->get_decl()->get_name().str().compare (0, 3, "sk!") == 0) { - m_out.push_back (n); + void operator() (app *n) { + int idx; + if (is_zk_const(n, idx)) { + m_out.push_back(n); + if (idx > m_max) { + m_max = idx; + } } } void operator() (quantifier const *n) const {} }; } -void find_zk_const(expr *e, app_ref_vector &res) { +int find_zk_const(expr *e, app_ref_vector &res) { find_zk_const_ns::proc p(res); for_each_expr (p, e); + return p.m_max; } namespace has_zk_const_ns { @@ -363,8 +232,8 @@ struct found {}; struct proc { void operator() (var const *n) const {} void operator() (app const *n) const { - if (is_uninterp_const(n) && - n->get_decl()->get_name().str().compare(0, 3, "sk!") == 0) { + int idx; + if (is_zk_const(n, idx)) { throw found(); } } @@ -384,4 +253,26 @@ bool has_zk_const(expr *e){ return false; } +bool is_zk_const (const app *a, int &n) { + if (!is_uninterp_const(a)) return false; + + const symbol &name = a->get_decl()->get_name(); + if (name.str().compare (0, 3, "sk!") != 0) { + return false; + } + + n = std::stoi(name.str().substr(3)); + return true; +} +bool sk_lt_proc::operator()(const app *a1, const app *a2) { + if (a1 == a2) return false; + int n1, n2; + bool z1, z2; + z1 = is_zk_const(a1, n1); + z2 = is_zk_const(a2, n2); + if (z1 && z2) return n1 < n2; + if (z1 != z2) return z1; + return ast_lt_proc()(a1, a2); +} + } diff --git a/src/muz/spacer/spacer_manager.h b/src/muz/spacer/spacer_manager.h index f2382c15d..d592a30a8 100644 --- a/src/muz/spacer/spacer_manager.h +++ b/src/muz/spacer/spacer_manager.h @@ -13,6 +13,7 @@ Abstract: Author: Krystof Hoder (t-khoder) 2011-8-25. + Arie Gurfinkel Revision History: @@ -34,12 +35,10 @@ Revision History: #include "muz/spacer/spacer_util.h" #include "muz/spacer/spacer_sym_mux.h" #include "muz/spacer/spacer_farkas_learner.h" -#include "muz/spacer/spacer_smt_context_manager.h" #include "muz/base/dl_rule.h" - -namespace smt { -class context; -} +#include "solver/solver.h" +#include "solver/solver_pool.h" +namespace smt {class context;} namespace spacer { @@ -67,280 +66,74 @@ public: m_relation_info(relations) {} std::string to_string() const; - expr_ref to_expr() const; - void to_model(model_ref& md) const; - - void display(datalog::rule_manager& rm, ptr_vector const& rules, std::ostream& out) const; + void display(datalog::rule_manager& rm, + ptr_vector const& rules, + std::ostream& out) const; }; class manager { ast_manager& m; - mutable bool_rewriter m_brwr; - + // manager of multiplexed names sym_mux m_mux; - expr_ref m_background; - decl_vector m_o0_preds; - spacer::smt_context_manager m_contexts; - spacer::smt_context_manager m_contexts2; - spacer::smt_context_manager m_contexts3; - /** whenever we need an unique number, we get this one and increase */ - unsigned m_next_unique_num; - - - static std::vector get_state_suffixes(); unsigned n_index() const { return 0; } unsigned o_index(unsigned i) const { return i + 1; } - void add_new_state(func_decl * s); - public: - manager(unsigned max_num_contexts, ast_manager & manager); + manager(ast_manager & manager); ast_manager& get_manager() const { return m; } - bool_rewriter& get_brwr() const { return m_brwr; } - expr_ref mk_and(unsigned sz, expr* const* exprs); - expr_ref mk_and(expr_ref_vector const& exprs) - { - return mk_and(exprs.size(), exprs.c_ptr()); - } - expr_ref mk_and(expr* a, expr* b) - { - expr* args[2] = { a, b }; - return mk_and(2, args); - } - expr_ref mk_or(unsigned sz, expr* const* exprs); - expr_ref mk_or(expr_ref_vector const& exprs) - { - return mk_or(exprs.size(), exprs.c_ptr()); - } - - expr_ref mk_not_and(expr_ref_vector const& exprs); - - void get_or(expr* e, expr_ref_vector& result); + // management of mux names //"o" predicates stand for the old states and "n" for the new states func_decl * get_o_pred(func_decl * s, unsigned idx); func_decl * get_n_pred(func_decl * s); - /** - Marks symbol as non-model which means it will not appear in models collected by - get_state_cube_from_model function. - This is to take care of auxiliary symbols introduced by the disjunction relations - to relativize lemmas coming from disjuncts. - */ - void mark_as_non_model(func_decl * p) - { - m_mux.mark_as_non_model(p); - } - - - func_decl * const * begin_o0_preds() const { return m_o0_preds.begin(); } - func_decl * const * end_o0_preds() const { return m_o0_preds.end(); } - - bool is_state_pred(func_decl * p) const { return m_mux.is_muxed(p); } - func_decl * to_o0(func_decl * p) { return m_mux.conv(m_mux.get_primary(p), 0, o_index(0)); } - - bool is_o(func_decl * p, unsigned idx) const - { - return m_mux.has_index(p, o_index(idx)); - } - void get_o_index(func_decl* p, unsigned& idx) const - { - m_mux.try_get_index(p, idx); - SASSERT(idx != n_index()); - idx--; // m_mux has indices starting at 1 - } - bool is_o(expr* e, unsigned idx) const - { - return is_app(e) && is_o(to_app(e)->get_decl(), idx); - } - bool is_o(func_decl * p) const - { - unsigned idx; - return m_mux.try_get_index(p, idx) && idx != n_index(); - } - bool is_o(expr* e) const - { - return is_app(e) && is_o(to_app(e)->get_decl()); - } - bool is_n(func_decl * p) const - { - return m_mux.has_index(p, n_index()); - } - bool is_n(expr* e) const - { - return is_app(e) && is_n(to_app(e)->get_decl()); - } - - /** true if p should not appead in models propagates into child relations */ - bool is_non_model_sym(func_decl * p) const - { return m_mux.is_non_model_sym(p); } - - - /** true if f doesn't contain any n predicates */ - bool is_o_formula(expr * f) const - { - return !m_mux.contains(f, n_index()); - } - - /** true if f contains only o state preds of index o_idx */ - bool is_o_formula(expr * f, unsigned o_idx) const - { - return m_mux.is_homogenous_formula(f, o_index(o_idx)); - } - /** true if f doesn't contain any o predicates */ bool is_n_formula(expr * f) const - { - return m_mux.is_homogenous_formula(f, n_index()); - } + {return m_mux.is_homogenous_formula(f, n_index());} func_decl * o2n(func_decl * p, unsigned o_idx) const - { - return m_mux.conv(p, o_index(o_idx), n_index()); - } + {return m_mux.shift_decl(p, o_index(o_idx), n_index());} func_decl * o2o(func_decl * p, unsigned src_idx, unsigned tgt_idx) const - { - return m_mux.conv(p, o_index(src_idx), o_index(tgt_idx)); - } + {return m_mux.shift_decl(p, o_index(src_idx), o_index(tgt_idx));} func_decl * n2o(func_decl * p, unsigned o_idx) const - { - return m_mux.conv(p, n_index(), o_index(o_idx)); - } + {return m_mux.shift_decl(p, n_index(), o_index(o_idx));} - void formula_o2n(expr * f, expr_ref & result, unsigned o_idx, bool homogenous = true) const - { m_mux.conv_formula(f, o_index(o_idx), n_index(), result, homogenous); } + void formula_o2n(expr * f, expr_ref & result, unsigned o_idx, + bool homogenous = true) const + {m_mux.shift_expr(f, o_index(o_idx), n_index(), result, homogenous);} - void formula_n2o(expr * f, expr_ref & result, unsigned o_idx, bool homogenous = true) const - { m_mux.conv_formula(f, n_index(), o_index(o_idx), result, homogenous); } + void formula_n2o(expr * f, expr_ref & result, unsigned o_idx, + bool homogenous = true) const + {m_mux.shift_expr(f, n_index(), o_index(o_idx), result, homogenous);} void formula_n2o(unsigned o_idx, bool homogenous, expr_ref & result) const - { m_mux.conv_formula(result.get(), n_index(), o_index(o_idx), result, homogenous); } + {m_mux.shift_expr(result.get(), n_index(), o_index(o_idx), + result, homogenous);} - void formula_o2o(expr * src, expr_ref & tgt, unsigned src_idx, unsigned tgt_idx, bool homogenous = true) const - { m_mux.conv_formula(src, o_index(src_idx), o_index(tgt_idx), tgt, homogenous); } + void formula_o2o(expr * src, expr_ref & tgt, unsigned src_idx, + unsigned tgt_idx, bool homogenous = true) const + {m_mux.shift_expr(src, o_index(src_idx), o_index(tgt_idx), + tgt, homogenous);} - /** - Return true if all state symbols which e contains are of one kind (either "n" or one of "o"). - */ - bool is_homogenous_formula(expr * e) const - { - return m_mux.is_homogenous_formula(e); - } - - /** - Collect indices used in expression. - */ - void collect_indices(expr* e, unsigned_vector& indices) const - { - m_mux.collect_indices(e, indices); - } - - /** - Collect used variables of each index. - */ - void collect_variables(expr* e, vector >& vars) const - { - m_mux.collect_variables(e, vars); - } - - /** - Return true iff both s1 and s2 are either "n" or "o" of the same index. - If one (or both) of them are not state symbol, return false. - */ - bool have_different_state_kinds(func_decl * s1, func_decl * s2) const - { - unsigned i1, i2; - return m_mux.try_get_index(s1, i1) && m_mux.try_get_index(s2, i2) && i1 != i2; - } - - /** - Increase indexes of state symbols in formula by dist. - The 'N' index becomes 'O' index with number dist-1. - */ - void formula_shift(expr * src, expr_ref & tgt, unsigned dist) const - { - SASSERT(n_index() == 0); - SASSERT(o_index(0) == 1); - m_mux.shift_formula(src, dist, tgt); - } - - void mk_model_into_cube(const expr_ref_vector & mdl, expr_ref & res); - void mk_core_into_cube(const expr_ref_vector & core, expr_ref & res); - void mk_cube_into_lemma(expr * cube, expr_ref & res); - void mk_lemma_into_cube(expr * lemma, expr_ref & res); - - /** - Remove from vec all atoms that do not have an "o" state. - The order of elements in vec may change. - An assumption is that atoms having "o" state of given index - do not have "o" states of other indexes or "n" states. - */ - void filter_o_atoms(expr_ref_vector& vec, unsigned o_idx) const - { m_mux.filter_idx(vec, o_index(o_idx)); } - void filter_n_atoms(expr_ref_vector& vec) const - { m_mux.filter_idx(vec, n_index()); } - - /** - Partition literals into o_lits and others. - */ - void partition_o_atoms(expr_ref_vector const& lits, - expr_ref_vector& o_lits, - expr_ref_vector& other, - unsigned o_idx) const - { - m_mux.partition_o_idx(lits, o_lits, other, o_index(o_idx)); - } - - void filter_out_non_model_atoms(expr_ref_vector& vec) const - { m_mux.filter_non_model_lits(vec); } - - bool try_get_state_and_value_from_atom(expr * atom, app *& state, app_ref& value); - bool try_get_state_decl_from_atom(expr * atom, func_decl *& state); - - - std::string pp_model(const model_core & mdl) const - { return m_mux.pp_model(mdl); } - - - void set_background(expr* b) { m_background = b; } - - expr* get_background() const { return m_background; } - - unsigned get_unique_num() { return m_next_unique_num++; } - - solver* mk_fresh() {return m_contexts.mk_fresh();} - smt_params& fparams() { return m_contexts.fparams(); } - solver* mk_fresh2() {return m_contexts2.mk_fresh();} - smt_params &fparams2() { return m_contexts2.fparams(); } - solver* mk_fresh3() {return m_contexts3.mk_fresh();} - smt_params &fparams3() {return m_contexts3.fparams();} - - - - void collect_statistics(statistics& st) const - { - m_contexts.collect_statistics(st); - m_contexts2.collect_statistics(st); - m_contexts3.collect_statistics(st); - } - - void reset_statistics() - { - m_contexts.reset_statistics(); - m_contexts2.reset_statistics(); - m_contexts3.reset_statistics(); - } }; +/** Skolem constants for quantified spacer */ app* mk_zk_const (ast_manager &m, unsigned idx, sort *s); -void find_zk_const(expr* e, app_ref_vector &out); +int find_zk_const(expr* e, app_ref_vector &out); +inline int find_zk_const(expr_ref_vector const &v, app_ref_vector &out) +{return find_zk_const (mk_and(v), out);} + bool has_zk_const(expr* e); +bool is_zk_const (const app *a, int &n); + +struct sk_lt_proc {bool operator()(const app* a1, const app* a2);}; + } #endif diff --git a/src/muz/spacer/spacer_mbc.cpp b/src/muz/spacer/spacer_mbc.cpp new file mode 100644 index 000000000..df0ce1cb4 --- /dev/null +++ b/src/muz/spacer/spacer_mbc.cpp @@ -0,0 +1,102 @@ +#include + +#include "muz/spacer/spacer_mbc.h" +#include "ast/rewriter/rewriter.h" +#include "ast/rewriter/rewriter_def.h" +#include "ast/rewriter/th_rewriter.h" +#include "ast/scoped_proof.h" +#include "model/model_evaluator.h" + + +namespace spacer { + +mbc::mbc(ast_manager &m) : m(m) {} + +namespace { +class mbc_rewriter_cfg : public default_rewriter_cfg { + + ast_manager &m; + const mbc::partition_map &m_pmap; + obj_map &m_subs; + model &m_mdl; + model_evaluator m_mev; + vector &m_parts; + unsigned m_current_part; + +public: + mbc_rewriter_cfg(ast_manager &m, const mbc::partition_map &pmap, + obj_map &subs, + model &mdl, vector &parts) : + m(m), m_pmap(pmap), m_subs(subs), m_mdl(mdl), m_mev(m_mdl), + m_parts(parts), m_current_part(UINT_MAX) + {m_mev.set_model_completion(true);} + + bool get_subst(expr *s, expr * & t, proof * & t_pr) { + if (!is_app(s)) return false; + unsigned part = UINT_MAX; + + // not in partition map + if (!m_pmap.find (to_app(s)->get_decl(), part)) return false; + + // first part element, remember it + if (!found_partition()) { + set_partition(part); + return false; + } + + // already in our substitution map + expr *tmp = nullptr; + if (m_subs.find(s, tmp)) { + t = tmp; + return true; + } + + // decide value based on model + expr_ref val(m); + + // eval in the model + m_mev.eval(s, val, true); + + // store decided equality (also keeps ref to s and val) + m_parts[part].push_back(m.mk_eq(s, val)); + // store substitution + m_subs.insert(s, val); + t = val; + return true; + } + + + void reset() {reset_partition();}; + void reset_partition() {m_current_part = UINT_MAX;} + unsigned partition() {return m_current_part;} + bool found_partition() {return m_current_part < UINT_MAX;} + void set_partition(unsigned v) {m_current_part = v;} +}; +} + +void mbc::operator()(const partition_map &pmap, expr_ref_vector &lits, + model &mdl, vector &res) { + scoped_no_proof _sp (m); + + obj_map subs; + mbc_rewriter_cfg cfg(m, pmap, subs, mdl, res); + rewriter_tpl rw(m, false, cfg); + th_rewriter thrw(m); + + for (auto *lit : lits) { + expr_ref new_lit(m); + rw.reset(); + rw(lit, new_lit); + thrw(new_lit); + if (cfg.found_partition()) { + SASSERT(cfg.partition() < res.size()); + res[cfg.partition()].push_back(new_lit); + } + } + + TRACE("mbc", tout << "Input: " << lits << "\n" + << "Output: \n"; + for (auto &vec : res) tout << vec << "\n==================\n";); +} + +} diff --git a/src/muz/spacer/spacer_mbc.h b/src/muz/spacer/spacer_mbc.h new file mode 100644 index 000000000..5dbf50f6d --- /dev/null +++ b/src/muz/spacer/spacer_mbc.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2018 Arie Gurfinkel + +Module Name: + + spacer_mbc.h + +Abstract: + + Model-Based Cartesian Decomposition + +Author: + + Arie Gurfinkel + +Revision History: + +--*/ + +#ifndef _SPACER_MBC_H_ +#define _SPACER_MBC_H_ + +#include "ast/ast.h" +#include "util/obj_hashtable.h" +#include "model/model.h" + +namespace spacer { + +class mbc { + ast_manager &m; +public: + mbc(ast_manager &m); + + + typedef obj_map partition_map; + + /** + \Brief Model Based Cartesian projection of lits + */ + void operator()(const partition_map &pmap, expr_ref_vector &lits, model &mdl, + vector &res); +}; + +} +#endif diff --git a/src/muz/spacer/spacer_pdr.cpp b/src/muz/spacer/spacer_pdr.cpp new file mode 100644 index 000000000..4e6b37a0a --- /dev/null +++ b/src/muz/spacer/spacer_pdr.cpp @@ -0,0 +1,375 @@ +/**++ +Copyright (c) 2018 Arie Gurfinkel + +Module Name: + + spacer_pdr.h + +Abstract: + + SPACER gPDR strategy implementation + +Author: + + Arie Gurfinkel + + Based on muz/pdr + +Notes: + +--*/ +#include "muz/spacer/spacer_pdr.h" +#include "muz/base/dl_context.h" +#include "muz/spacer/spacer_mbc.h" + +namespace spacer { +model_node::model_node(model_node* parent, class pob *pob): + m_pob(pob), m_parent(parent), m_next(nullptr), m_prev(nullptr), + m_orig_level(m_pob->level()), m_depth(0), + m_closed(false) { + SASSERT(m_pob); + if (m_parent) m_parent->add_child(this); +} + +void model_node::add_child(model_node* kid) { + m_children.push_back(kid); + SASSERT(level() == kid->level() + 1); + SASSERT(level() > 0); + kid->m_depth = m_depth + 1; + if (is_closed()) set_open(); +} + +unsigned model_node::index_in_parent() const { + if (!m_parent) return 0; + for (unsigned i = 0, sz = m_parent->children().size(); i < sz; ++i) { + if (this == m_parent->children().get(i)) return i; + } + UNREACHABLE(); + return 0; +} + +void model_node::check_pre_closed() { + for (auto *kid : m_children) {if (kid->is_open()) return;} + + set_pre_closed(); + model_node* p = m_parent; + while (p && p->is_1closed()) { + p->set_pre_closed(); + p = p->parent(); + } +} +void model_node::set_open() { + SASSERT(m_closed); + m_closed = false; + model_node* p = parent(); + while (p && p->is_closed()) { + p->m_closed = false; + p = p->parent(); + } +} + +void model_node::detach(model_node*& qhead) { + SASSERT(in_queue()); + SASSERT(children().empty()); + if (this == m_next) { + SASSERT(m_prev == this); + SASSERT(this == qhead); + qhead = nullptr; + } + else { + m_next->m_prev = m_prev; + m_prev->m_next = m_next; + if (this == qhead) qhead = m_next; + } + + // detach + m_prev = nullptr; + m_next = nullptr; +} + + +// insert node n after this in the queue +// requires: this is in a queue or this == n +void model_node::insert_after(model_node* n) { + SASSERT(this == n || in_queue()); + SASSERT(n); + SASSERT(!n->in_queue()); + if (this == n) { + m_next = n; + m_prev = n; + } + else { + n->m_next = m_next; + m_next->m_prev = n; + m_next = n; + n->m_prev = this; + } +} + +void model_search::reset() { + if (m_root) { + erase_children(*m_root, false); + remove_node(m_root, false); + dealloc(m_root); + m_root = nullptr; + } + m_cache.reset(); +} + +model_node* model_search::pop_front() { + model_node *res = m_qhead; + if (res) { + res->detach(m_qhead); + } + return res; +} + +void model_search::add_leaf(model_node* _n) { + model_node& n = *_n; + SASSERT(n.children().empty()); + model_nodes ns; + model_nodes& nodes = cache(n).insert_if_not_there2(n.post(), ns)->get_data().m_value; + if (nodes.contains(&n)) return; + + nodes.push_back(_n); + if (nodes.size() == 1) { + SASSERT(n.is_open()); + enqueue_leaf(n); + } + else { + n.set_pre_closed(); + } +} + +void model_search::enqueue_leaf(model_node& n) { + SASSERT(n.is_open()); + SASSERT(!n.in_queue()); + // queue is empty, initialize it with n + if (!m_qhead) { + m_qhead = &n; + m_qhead->insert_after(m_qhead); + } + // insert n after m_qhead + else if (m_bfs) { + m_qhead->insert_after(&n); + } + // insert n after m_qhead()->next() + else { + m_qhead->next()->insert_after(&n); + } +} + + + +void model_search::set_root(model_node* root) { + reset(); + m_root = root; + SASSERT(m_root); + SASSERT(m_root->children().empty()); + add_leaf(root); +} + +void model_search::backtrack_level(bool uses_level, model_node& n) { + SASSERT(m_root); + if (uses_level) {NOT_IMPLEMENTED_YET();} + if (uses_level && m_root->level() > n.level()) { + n.increase_level(); + enqueue_leaf(n); + } + else { + model_node* p = n.parent(); + if (p) { + erase_children(*p, true); + enqueue_leaf(*p); + } + } +} + +obj_map >& model_search::cache(model_node const& n) { + unsigned l = n.orig_level(); + if (l >= m_cache.size()) m_cache.resize(l + 1); + return m_cache[l]; +} + +void model_search::erase_children(model_node& n, bool backtrack) { + ptr_vector todo, nodes; + todo.append(n.children()); + // detach n from queue + if (n.in_queue()) n.detach(m_qhead); + n.reset_children(); + while (!todo.empty()) { + model_node* m = todo.back(); + todo.pop_back(); + nodes.push_back(m); + todo.append(m->children()); + remove_node(m, backtrack); + } + std::for_each(nodes.begin(), nodes.end(), delete_proc()); +} + +// removes node from the search tree and from the cache +void model_search::remove_node(model_node* _n, bool backtrack) { + model_node& n = *_n; + model_nodes& nodes = cache(n).find(n.post()); + nodes.erase(_n); + if (n.in_queue()) n.detach(m_qhead); + // TBD: siblings would also fail if n is not a goal. + if (!nodes.empty() && backtrack && + nodes[0]->children().empty() && nodes[0]->is_closed()) { + model_node* n1 = nodes[0]; + n1->set_open(); + enqueue_leaf(*n1); + } + + if (nodes.empty()) cache(n).remove(n.post()); +} + + +lbool context::gpdr_solve_core() { + scoped_watch _w_(m_solve_watch); + //if there is no query predicate, abort + if (!m_rels.find(m_query_pred, m_query)) { return l_false; } + + model_search ms(m_pdr_bfs); + unsigned lvl = 0; + unsigned max_level = m_max_level; + for (lvl = 0; lvl < max_level; ++lvl) { + checkpoint(); + IF_VERBOSE(1,verbose_stream() << "GPDR Entering level "<< lvl << "\n";); + STRACE("spacer.expand-add", tout << "\n* LEVEL " << lvl << "\n";); + m_expanded_lvl = infty_level(); + m_stats.m_max_query_lvl = lvl; + if (gpdr_check_reachability(lvl, ms)) {return l_true;} + if (lvl > 0) { + if (propagate(m_expanded_lvl, lvl, UINT_MAX)) {return l_false;} + } + } + + // communicate failure to datalog::context + if (m_context) { + m_context->set_status(datalog::BOUNDED); + } + return l_undef; +} + +bool context::gpdr_check_reachability(unsigned lvl, model_search &ms) { + pob_ref root_pob = m_query->mk_pob(nullptr, lvl, 0, m.mk_true()); + model_node *root_node = alloc(model_node, nullptr, root_pob.get()); + + ms.set_root(root_node); + pob_ref_buffer new_pobs; + + while (model_node *node = ms.pop_front()) { + IF_VERBOSE(2, verbose_stream() << "Expand node: " + << node->level() << "\n";); + new_pobs.reset(); + checkpoint(); + pred_transformer &pt = node->pt(); + + // check reachable cache + if (pt.is_must_reachable(node->pob()->post(), nullptr)) { + TRACE("spacer", + tout << "must-reachable: " << pt.head()->get_name() << " level: " + << node->level() << " depth: " << node->depth () << "\n"; + tout << mk_pp(node->pob()->post(), m) << "\n";); + + node->set_closed(); + if (node == root_node) return true; + continue; + } + + switch (expand_pob(*node->pob(), new_pobs)){ + case l_true: + node->set_closed(); + if (node == root_node) return true; + break; + case l_false: + ms.backtrack_level(false, *node); + if (node == root_node) return false; + break; + case l_undef: + SASSERT(!new_pobs.empty()); + for (auto pob : new_pobs) { + TRACE("spacer_pdr", + tout << "looking at pob at level " << pob->level() << " " + << mk_pp(pob->post(), m) << "\n";); + if (pob != node->pob()) + ms.add_leaf(alloc(model_node, node, pob)); + } + node->check_pre_closed(); + break; + } + } + + return root_node->is_closed(); +} + +bool context::gpdr_create_split_children(pob &n, const datalog::rule &r, + expr *trans, + model &mdl, + pob_ref_buffer &out) { + pred_transformer &pt = n.pt(); + ptr_vector preds; + pt.find_predecessors(r, preds); + SASSERT(preds.size() > 1); + + ptr_vector ppts; + for (auto *p : preds) ppts.push_back(&get_pred_transformer(p)); + + mbc::partition_map pmap; + for (unsigned i = 0, sz = preds.size(); i < sz; ++i) { + func_decl *p = preds.get(i); + pred_transformer &ppt = *ppts.get(i); + for (unsigned j = 0, jsz = p->get_arity(); j < jsz; ++j) { + pmap.insert(m_pm.o2o(ppt.sig(j), 0, i), i); + } + } + + + spacer::mbc _mbc(m); + expr_ref_vector lits(m); + flatten_and(trans, lits); + vector res(preds.size(), expr_ref_vector(m)); + _mbc(pmap, lits, mdl, res); + + // pick an order to process children + unsigned_vector kid_order; + kid_order.resize(preds.size(), 0); + for (unsigned i = 0, sz = preds.size(); i < sz; ++i) kid_order[i] = i; + if (m_children_order == CO_REV_RULE) { + kid_order.reverse(); + } + else if (m_children_order == CO_RANDOM) { + shuffle(kid_order.size(), kid_order.c_ptr(), m_random); + } + + + for (unsigned i = 0, sz = res.size(); i < sz; ++i) { + unsigned j = kid_order[i]; + expr_ref post(m); + pred_transformer &ppt = *ppts.get(j); + post = mk_and(res.get(j)); + m_pm.formula_o2n(post.get(), post, j, true); + pob * k = ppt.mk_pob(&n, prev_level(n.level()), n.depth(), post); + out.push_back(k); + IF_VERBOSE (1, verbose_stream() + << "\n\tcreate_child: " << k->pt().head()->get_name() + << " (" << k->level() << ", " << k->depth() << ") " + << (k->use_farkas_generalizer() ? "FAR " : "SUB ") + << k->post()->get_id(); + verbose_stream().flush();); + TRACE ("spacer", + tout << "create-pob: " << k->pt().head()->get_name() + << " level: " << k->level() + << " depth: " << k->depth () + << " fvsz: " << k->get_free_vars_size() << "\n" + << mk_pp(k->post(), m) << "\n";); + + + } + + return true; +} + + +} // spacer diff --git a/src/muz/spacer/spacer_pdr.h b/src/muz/spacer/spacer_pdr.h new file mode 100644 index 000000000..36abb0bc9 --- /dev/null +++ b/src/muz/spacer/spacer_pdr.h @@ -0,0 +1,107 @@ +/**++ +Copyright (c) 2018 Arie Gurfinkel + +Module Name: + + spacer_pdr.h + +Abstract: + + SPACER gPDR strategy implementation + +Author: + + Arie Gurfinkel + + Based on muz/pdr + +Notes: + +--*/ +#ifndef _SPACER_PDR_H_ +#define _SPACER_PDR_H_ + +#include "muz/spacer/spacer_context.h" + +namespace spacer { +// structure for counter-example search. +class model_node { + pob_ref m_pob; // proof obligation + model_node* m_parent; // parent in the search tree + ptr_vector m_children; // children in the search tree + model_node* m_next; // next element of an in-place circular queue + model_node* m_prev; // prev element of an in-place circular queue + unsigned m_orig_level; // level at which this search node was created + unsigned m_depth; // + bool m_closed; // whether the pob is derivable +public: + model_node(model_node* parent, pob* pob); + void add_child(model_node* kid); + + expr *post() const { return m_pob->post(); } + unsigned level() const { return m_pob->level(); } + unsigned orig_level() const { return m_orig_level; } + unsigned depth() const { return m_depth; } + void increase_level() { m_pob->inc_level(); } + pob_ref &pob() { return m_pob; } + ptr_vector const& children() { return m_children; } + pred_transformer& pt() const { return m_pob->pt(); } + model_node* parent() const { return m_parent; } + // order in children of the parent + unsigned index_in_parent() const; + + bool is_closed() const { return m_closed; } + bool is_open() const { return !is_closed(); } + + // closed or has children and they are all closed + bool is_1closed() { + if (is_closed()) return true; + if (m_children.empty()) return false; + for (auto kid : m_children) + if (kid->is_open()) return false; + return true; + } + + void check_pre_closed(); + void set_pre_closed() { m_closed = true; } + + void set_closed() { m_closed = true; } + void set_open(); + void reset_children() { m_children.reset(); } + + /// queue + + // remove this node from the given queue + void detach(model_node*& qhead); + void insert_after(model_node* n); + model_node* next() const { return m_next; } + bool in_queue() { return m_next && m_prev; } +}; + +class model_search { + typedef ptr_vector model_nodes; + bool m_bfs; + model_node* m_root; + model_node* m_qhead; + vector > m_cache; + obj_map& cache(model_node const& n); + void erase_children(model_node& n, bool backtrack); + void remove_node(model_node* _n, bool backtrack); + +public: + model_search(bool bfs): m_bfs(bfs), m_root(nullptr), m_qhead(nullptr) {} + ~model_search() {reset();} + + void set_root(model_node* n); + + void reset(); + model_node* pop_front(); + void add_leaf(model_node* n); // add fresh node. + model_node& get_root() const { return *m_root; } + void backtrack_level(bool uses_level, model_node& n); + void remove_goal(model_node& n); + + void enqueue_leaf(model_node &n); +}; +} +#endif diff --git a/src/muz/spacer/spacer_proof_utils.cpp b/src/muz/spacer/spacer_proof_utils.cpp new file mode 100644 index 000000000..082cc4b5d --- /dev/null +++ b/src/muz/spacer/spacer_proof_utils.cpp @@ -0,0 +1,555 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_proof_utils.cpp + +Abstract: + Utilities to traverse and manipulate proofs + +Author: + Bernhard Gleiss + Arie Gurfinkel + +Revision History: + +--*/ + +#include "util/params.h" +#include "ast/ast_pp.h" +#include "ast/ast_util.h" +#include "ast/proofs/proof_checker.h" +#include "muz/base/dl_util.h" +#include "muz/spacer/spacer_iuc_proof.h" + +#include "ast/proofs/proof_utils.h" +#include "muz/spacer/spacer_proof_utils.h" +#include "muz/spacer/spacer_util.h" + +namespace spacer { + +// arithmetic lemma recognizer +bool is_arith_lemma(ast_manager& m, proof* pr) +{ + // arith lemmas: second parameter specifies exact type of lemma, + // could be "farkas", "triangle-eq", "eq-propagate", + // "assign-bounds", maybe also something else + if (pr->get_decl_kind() == PR_TH_LEMMA) { + func_decl* d = pr->get_decl(); + symbol sym; + return d->get_num_parameters() >= 1 && + d->get_parameter(0).is_symbol(sym) && + sym == "arith"; + } + return false; +} + +// farkas lemma recognizer +bool is_farkas_lemma(ast_manager& m, proof* pr) +{ + if (pr->get_decl_kind() == PR_TH_LEMMA) + { + func_decl* d = pr->get_decl(); + symbol sym; + return d->get_num_parameters() >= 2 && + d->get_parameter(0).is_symbol(sym) && sym == "arith" && + d->get_parameter(1).is_symbol(sym) && sym == "farkas"; + } + return false; +} + + +/* + * ==================================== + * methods for transforming proofs + * ==================================== + */ + +void theory_axiom_reducer::reset() { + m_cache.reset(); + m_pinned.reset(); +} + +// -- rewrite theory axioms into theory lemmas +proof_ref theory_axiom_reducer::reduce(proof* pr) { + proof_post_order pit(pr, m); + while (pit.hasNext()) { + proof* p = pit.next(); + + if (m.get_num_parents(p) == 0 && is_arith_lemma(m, p)) { + // we have an arith-theory-axiom and want to get rid of it + // we need to replace the axiom with + // (a) corresponding hypothesis, + // (b) a theory lemma, and + // (c) a lemma. + // Furthermore update data-structures + app *fact = to_app(m.get_fact(p)); + ptr_buffer cls; + if (m.is_or(fact)) { + for (unsigned i = 0, sz = fact->get_num_args(); i < sz; ++i) + cls.push_back(fact->get_arg(i)); + } + else + cls.push_back(fact); + + // (a) create hypothesis + ptr_buffer hyps; + for (unsigned i = 0, sz = cls.size(); i < sz; ++i) { + expr *c; + expr_ref hyp_fact(m); + if (m.is_not(cls[i], c)) + hyp_fact = c; + else + hyp_fact = m.mk_not (cls[i]); + + proof* hyp = m.mk_hypothesis(hyp_fact); + m_pinned.push_back(hyp); + hyps.push_back(hyp); + } + + // (b) create farkas lemma. Rebuild parameters since + // mk_th_lemma() adds tid as first parameter + unsigned num_params = p->get_decl()->get_num_parameters(); + parameter const* params = p->get_decl()->get_parameters(); + vector parameters; + for (unsigned i = 1; i < num_params; ++i) parameters.push_back(params[i]); + + SASSERT(params[0].is_symbol()); + family_id tid = m.mk_family_id(params[0].get_symbol()); + SASSERT(tid != null_family_id); + + proof* th_lemma = m.mk_th_lemma(tid, m.mk_false(), + hyps.size(), hyps.c_ptr(), + num_params-1, parameters.c_ptr()); + m_pinned.push_back(th_lemma); + SASSERT(is_arith_lemma(m, th_lemma)); + + // (c) create lemma + proof* res = m.mk_lemma(th_lemma, fact); + m_pinned.push_back(res); + m_cache.insert(p, res); + + SASSERT(m.get_fact(res) == m.get_fact(p)); + } + else { + // proof is dirty, if a sub-proof of one of its premises + // has been transformed + bool dirty = false; + + ptr_buffer args; + for (unsigned i = 0, sz = m.get_num_parents(p); i < sz; ++i) { + proof *pp, *tmp; + pp = m.get_parent(p, i); + VERIFY(m_cache.find(pp, tmp)); + args.push_back(tmp); + dirty |= (pp != tmp); + } + // if not dirty just use the old step + if (!dirty) m_cache.insert(p, p); + // otherwise create new proof with the corresponding proofs + // of the premises + else { + if (m.has_fact(p)) args.push_back(m.get_fact(p)); + + SASSERT(p->get_decl()->get_arity() == args.size()); + + proof* res = m.mk_app(p->get_decl(), + args.size(), (expr * const*)args.c_ptr()); + m_pinned.push_back(res); + m_cache.insert(p, res); + } + } + } + + proof* res; + VERIFY(m_cache.find(pr,res)); + DEBUG_CODE( + proof_checker pc(m); + expr_ref_vector side(m); + SASSERT(pc.check(res, side)); + ); + + return proof_ref(res, m); +} + +/* ------------------------------------------------------------------------- */ +/* hypothesis_reducer */ +/* ------------------------------------------------------------------------- */ + +proof_ref hypothesis_reducer::reduce(proof* pr) { + compute_hypsets(pr); + collect_units(pr); + + proof_ref res(reduce_core(pr), m); + SASSERT(res); + reset(); + + DEBUG_CODE(proof_checker pc(m); + expr_ref_vector side(m); + SASSERT(pc.check(res, side));); + return res; +} + +void hypothesis_reducer::reset() { + m_active_hyps.reset(); + m_units.reset(); + m_cache.reset(); + for (auto t : m_pinned_active_hyps) dealloc(t); + m_pinned_active_hyps.reset(); + m_pinned.reset(); + m_hyp_mark.reset(); + m_open_mark.reset(); + m_visited.reset(); +} + +void hypothesis_reducer::compute_hypsets(proof *pr) { + ptr_buffer todo; + todo.push_back(pr); + + while (!todo.empty()) { + proof* p = todo.back(); + + if (m_visited.is_marked(p)) { + todo.pop_back(); + continue; + } + + unsigned todo_sz = todo.size(); + for (unsigned i = 0, sz = m.get_num_parents(p); i < sz; ++i) { + SASSERT(m.is_proof(p->get_arg(i))); + proof *parent = to_app(p->get_arg(i)); + + if (!m_visited.is_marked(parent)) + todo.push_back(parent); + } + if (todo.size() > todo_sz) continue; + + todo.pop_back(); + + m_visited.mark(p); + + + proof_ptr_vector* active_hyps = nullptr; + // fill both sets + if (m.is_hypothesis(p)) { + // create active_hyps-set for step p + proof_ptr_vector* active_hyps = alloc(proof_ptr_vector); + m_pinned_active_hyps.insert(active_hyps); + m_active_hyps.insert(p, active_hyps); + active_hyps->push_back(p); + m_open_mark.mark(p); + m_hyp_mark.mark(m.get_fact(p)); + continue; + } + + ast_fast_mark1 seen; + + active_hyps = alloc(proof_ptr_vector); + for (unsigned i = 0, sz = m.get_num_parents(p); i < sz; ++i) { + proof* parent = m.get_parent(p, i); + // lemmas clear all hypotheses above them + if (m.is_lemma(p)) continue; + for (auto *x : *m_active_hyps.find(parent)) { + if (!seen.is_marked(x)) { + seen.mark(x); + active_hyps->push_back(x); + m_open_mark.mark(p); + } + } + } + if (active_hyps->empty()) { + dealloc(active_hyps); + m_active_hyps.insert(p, &m_empty_vector); + } + else { + m_pinned_active_hyps.push_back(active_hyps); + m_active_hyps.insert(p, active_hyps); + } + } +} + +// collect all units that are hyp-free and are used as hypotheses somewhere +// requires that m_active_hyps has been computed +void hypothesis_reducer::collect_units(proof* pr) { + + proof_post_order pit(pr, m); + while (pit.hasNext()) { + proof* p = pit.next(); + if (!m.is_hypothesis(p)) { + // collect units that are hyp-free and are used as + // hypotheses in the proof pr + if (!m_open_mark.is_marked(p) && m.has_fact(p) && + m_hyp_mark.is_marked(m.get_fact(p))) + m_units.insert(m.get_fact(p), p); + } + } +} + +/** + \brief returns true if p is an ancestor of q + */ +bool hypothesis_reducer::is_ancestor(proof *p, proof *q) { + if (p == q) return true; + ptr_vector todo; + todo.push_back(q); + + expr_mark visited; + while (!todo.empty()) { + proof *cur; + cur = todo.back(); + todo.pop_back(); + + if (visited.is_marked(cur)) continue; + + if (cur == p) return true; + visited.mark(cur); + + for (unsigned i = 0, sz = m.get_num_parents(cur); i < sz; ++i) { + todo.push_back(m.get_parent(cur, i)); + } + } + return false; +} + +proof* hypothesis_reducer::reduce_core(proof* pf) { + SASSERT(m.is_false(m.get_fact(pf))); + + proof *res = NULL; + + ptr_vector todo; + todo.push_back(pf); + ptr_buffer args; + bool dirty = false; + + while (true) { + proof *p, *tmp, *pp; + unsigned todo_sz; + + p = todo.back(); + if (m_cache.find(p, tmp)) { + todo.pop_back(); + continue; + } + + dirty = false; + args.reset(); + todo_sz = todo.size(); + for (unsigned i = 0, sz = m.get_num_parents(p); i < sz; ++i) { + pp = m.get_parent(p, i); + if (m_cache.find(pp, tmp)) { + args.push_back(tmp); + dirty |= pp != tmp; + } else { + todo.push_back(pp); + } + } + + if (todo_sz < todo.size()) continue; + + todo.pop_back(); + + // transform the current proof node + + if (m.is_hypothesis(p)) { + // if possible, replace a hypothesis by a unit derivation + if (m_units.find(m.get_fact(p), tmp)) { + // use already transformed proof of the unit if it is available + proof* proof_of_unit; + if (!m_cache.find(tmp, proof_of_unit)) { + proof_of_unit = tmp; + } + + // make sure hypsets for the unit are computed + // AG: is this needed? + compute_hypsets(proof_of_unit); + + // if the transformation doesn't create a cycle, perform it + if (!is_ancestor(p, proof_of_unit)) { + res = proof_of_unit; + } + else { + // -- failed to transform the proof, perhaps bad + // -- choice of the proof of unit + res = p; + } + } + else { + // -- no unit found to replace the hypothesis + res = p; + } + } + + else if (!dirty) {res = p;} + + else if (m.is_lemma(p)) { + // lemma: reduce the premise; remove reduced consequences + // from conclusion + SASSERT(args.size() == 1); + res = mk_lemma_core(args[0], m.get_fact(p)); + // -- re-compute hypsets + compute_hypsets(res); + } + else if (m.is_unit_resolution(p)) { + // unit: reduce untis; reduce the first premise; rebuild + // unit resolution + res = mk_unit_resolution_core(p, args); + // -- re-compute hypsets + compute_hypsets(res); + } + else { + res = mk_proof_core(p, args); + // -- re-compute hypsets + compute_hypsets(res); + } + + SASSERT(res); + m_cache.insert(p, res); + + // bail out as soon as found a sub-proof of false + if (!m_open_mark.is_marked(res) && m.has_fact(res) && m.is_false(m.get_fact(res))) + return res; + } + UNREACHABLE(); + return nullptr; +} + +proof* hypothesis_reducer::mk_lemma_core(proof* premise, expr *fact) { + SASSERT(m.is_false(m.get_fact(premise))); + SASSERT(m_active_hyps.contains(premise)); + + proof_ptr_vector* active_hyps = m_active_hyps.find(premise); + + // if there is no active hypothesis return the premise + if (!m_open_mark.is_marked(premise)) { + // XXX just in case premise might go away + m_pinned.push_back(premise); + return premise; + } + + // add some stability + std::stable_sort(active_hyps->begin(), active_hyps->end(), ast_lt_proc()); + // otherwise, build a disjunction of the negated active hypotheses + // and add a lemma proof step + expr_ref_buffer args(m); + for (auto hyp : *active_hyps) { + expr *hyp_fact, *t; + hyp_fact = m.get_fact(hyp); + if (m.is_not(hyp_fact, t)) + args.push_back(t); + else + args.push_back(m.mk_not(hyp_fact)); + } + + expr_ref lemma(m); + lemma = mk_or(m, args.size(), args.c_ptr()); + + proof* res; + res = m.mk_lemma(premise, lemma); + m_pinned.push_back(res); + return res; +} + +proof* hypothesis_reducer::mk_unit_resolution_core(proof *ures, + ptr_buffer& args) { + // if any literal is false, we don't need a unit resolution step + // This can be the case due to some previous transformations + for (unsigned i = 1, sz = args.size(); i < sz; ++i) { + if (m.is_false(m.get_fact(args[i]))) { + // XXX pin just in case + m_pinned.push_back(args[i]); + return args[i]; + } + } + + proof* arg0 = args[0]; + app *fact0 = to_app(m.get_fact(arg0)); + + + ptr_buffer pf_args; + ptr_buffer pf_fact; + pf_args.push_back(arg0); + + // compute literals to be resolved + ptr_buffer lits; + + // fact0 is a literal whenever the original resolution was a + // binary resolution to an empty clause + if (m.get_num_parents(ures) == 2 && m.is_false(m.get_fact(ures))) { + lits.push_back(fact0); + } + // fact0 is a literal unless it is a dijsunction + else if (!m.is_or(fact0)) { + lits.push_back(fact0); + } + // fact0 is a literal only if it appears as a literal in the + // original resolution + else { + lits.reset(); + app* ures_fact = to_app(m.get_fact(m.get_parent(ures, 0))); + for (unsigned i = 0, sz = ures_fact->get_num_args(); i < sz; ++i) { + if (ures_fact->get_arg(i) == fact0) { + lits.push_back(fact0); + break; + } + } + if (lits.empty()) { + lits.append(fact0->get_num_args(), fact0->get_args()); + } + + } + + // -- find all literals that are resolved on + for (unsigned i = 0, sz = lits.size(); i < sz; ++i) { + bool found = false; + for (unsigned j = 1; j < args.size(); ++j) { + if (m.is_complement(lits.get(i), m.get_fact(args[j]))) { + found = true; + pf_args.push_back(args[j]); + break; + } + } + if (!found) {pf_fact.push_back(lits.get(i));} + } + + // unit resolution got reduced to noop + if (pf_args.size() == 1) { + // XXX pin just in case + m_pinned.push_back(arg0); + + return arg0; + } + + // make unit resolution proof step + // expr_ref tmp(m); + // tmp = mk_or(m, pf_fact.size(), pf_fact.c_ptr()); + // proof* res = m.mk_unit_resolution(pf_args.size(), pf_args.c_ptr(), tmp); + proof *res = m.mk_unit_resolution(pf_args.size(), pf_args.c_ptr()); + m_pinned.push_back(res); + + return res; +} + +proof* hypothesis_reducer::mk_proof_core(proof* old, ptr_buffer& args) { + // if any of the literals are false, we don't need a step + for (unsigned i = 0; i < args.size(); ++i) { + if (m.is_false(m.get_fact(args[i]))) { + // XXX just in case + m_pinned.push_back(args[i]); + return args[i]; + } + } + + // otherwise build step + // BUG: I guess this doesn't work with quantifiers (since they are no apps) + args.push_back(to_app(m.get_fact(old))); + + SASSERT(old->get_decl()->get_arity() == args.size()); + + proof* res = m.mk_app(old->get_decl(), args.size(), + (expr * const*)args.c_ptr()); + m_pinned.push_back(res); + return res; +} + +}; diff --git a/src/muz/spacer/spacer_proof_utils.h b/src/muz/spacer/spacer_proof_utils.h new file mode 100644 index 000000000..ead85f885 --- /dev/null +++ b/src/muz/spacer/spacer_proof_utils.h @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_proof_utils.cpp + +Abstract: + Utilities to traverse and manipulate proofs + +Author: + Bernhard Gleiss + Arie Gurfinkel + +Revision History: + +--*/ + +#ifndef _SPACER_PROOF_UTILS_H_ +#define _SPACER_PROOF_UTILS_H_ +#include "ast/ast.h" + +namespace spacer { + +bool is_arith_lemma(ast_manager& m, proof* pr); +bool is_farkas_lemma(ast_manager& m, proof* pr); + +/// rewrites theory axioms into theory lemmas +class theory_axiom_reducer { +public: + theory_axiom_reducer(ast_manager& m) : m(m), m_pinned(m) {} + + // reduce theory axioms and return transformed proof + proof_ref reduce(proof* pr); + +private: + ast_manager &m; + + // tracking all created expressions + expr_ref_vector m_pinned; + + // maps each proof of a clause to the transformed subproof of + // that clause + obj_map m_cache; + + void reset(); +}; + +/// reduces the number of hypotheses in a proof +class hypothesis_reducer +{ +public: + hypothesis_reducer(ast_manager &m) : m(m), m_pinned(m) {} + ~hypothesis_reducer() {reset();} + + // reduce hypothesis and return transformed proof + proof_ref reduce(proof* pf); + +private: + typedef ptr_vector proof_ptr_vector; + + ast_manager &m; + + proof_ptr_vector m_empty_vector; + + // created expressions + expr_ref_vector m_pinned; + + // created sets of active hypothesis + ptr_vector m_pinned_active_hyps; + + // maps a proof to the transformed proof + obj_map m_cache; + + // maps a unit literal to its derivation + obj_map m_units; + + // maps a proof node to the set of its active (i.e., in scope) hypotheses + obj_map m_active_hyps; + + /// marks if an expression is ever used as a hypothesis in a proof + expr_mark m_hyp_mark; + /// marks a proof as open, i.e., has a non-discharged hypothesis as ancestor + expr_mark m_open_mark; + expr_mark m_visited; + + void reset(); + + /// true if p is an ancestor of q + bool is_ancestor(proof *p, proof *q); + // compute active_hyps and parent_hyps for a given proof node and + // all its ancestors + void compute_hypsets(proof* pr); + // compute m_units + void collect_units(proof* pr); + + // -- rewrite proof to reduce number of hypotheses used + proof* reduce_core(proof* pf); + + proof* mk_lemma_core(proof *pf, expr *fact); + proof* mk_unit_resolution_core(proof* ures, ptr_buffer& args); + proof* mk_proof_core(proof* old, ptr_buffer& args); +}; +} +#endif diff --git a/src/muz/spacer/spacer_prop_solver.cpp b/src/muz/spacer/spacer_prop_solver.cpp index 059374e39..7bd21a1b5 100644 --- a/src/muz/spacer/spacer_prop_solver.cpp +++ b/src/muz/spacer/spacer_prop_solver.cpp @@ -35,13 +35,15 @@ Revision History: #include "muz/spacer/spacer_farkas_learner.h" #include "muz/spacer/spacer_prop_solver.h" -#include "muz/base/fixedpoint_params.hpp" +#include "model/model_evaluator.h" +#include "muz/base/fp_params.hpp" namespace spacer { -prop_solver::prop_solver(manager& pm, fixedpoint_params const& p, symbol const& name) : - m(pm.get_manager()), - m_pm(pm), +prop_solver::prop_solver(ast_manager &m, + solver *solver0, solver *solver1, + fp_params const& p, symbol const& name) : + m(m), m_name(name), m_ctx(nullptr), m_pos_level_atoms(m), @@ -54,17 +56,21 @@ prop_solver::prop_solver(manager& pm, fixedpoint_params const& p, symbol const& m_use_push_bg(p.spacer_keep_proxy()) { - m_solvers[0] = pm.mk_fresh(); - m_fparams[0] = &pm.fparams(); + m_solvers[0] = solver0; + m_solvers[1] = solver1; - m_solvers[1] = pm.mk_fresh2(); - m_fparams[1] = &pm.fparams2(); - - m_contexts[0] = alloc(spacer::itp_solver, *(m_solvers[0]), p.spacer_new_unsat_core(), p.spacer_minimize_unsat_core(), p.spacer_farkas_optimized(), p.spacer_farkas_a_const(), p.spacer_split_farkas_literals()); - m_contexts[1] = alloc(spacer::itp_solver, *(m_solvers[1]), p.spacer_new_unsat_core(), p.spacer_minimize_unsat_core(), p.spacer_farkas_optimized(), p.spacer_farkas_a_const(), p.spacer_split_farkas_literals()); - - for (unsigned i = 0; i < 2; ++i) - { m_contexts[i]->assert_expr(m_pm.get_background()); } + m_contexts[0] = alloc(spacer::iuc_solver, *(m_solvers[0]), + p.spacer_iuc(), + p.spacer_iuc_arith(), + p.spacer_iuc_print_farkas_stats(), + p.spacer_iuc_old_hyp_reducer(), + p.spacer_iuc_split_farkas_literals()); + m_contexts[1] = alloc(spacer::iuc_solver, *(m_solvers[1]), + p.spacer_iuc(), + p.spacer_iuc_arith(), + p.spacer_iuc_print_farkas_stats(), + p.spacer_iuc_old_hyp_reducer(), + p.spacer_iuc_split_farkas_literals()); } void prop_solver::add_level() @@ -119,6 +125,8 @@ void prop_solver::assert_expr(expr * form) void prop_solver::assert_expr(expr * form, unsigned level) { + if (is_infty_level(level)) {assert_expr(form);return;} + ensure_level(level); app * lev_atom = m_pos_level_atoms[level].get(); app_ref lform(m.mk_or(form, lev_atom), m); @@ -126,18 +134,109 @@ void prop_solver::assert_expr(expr * form, unsigned level) } +/// Local model guided maxsmt +lbool prop_solver::mss(expr_ref_vector &hard, expr_ref_vector &soft) { + // replace expressions by assumption literals + iuc_solver::scoped_mk_proxy _p_(*m_ctx, hard); + unsigned hard_sz = hard.size(); + + lbool res = m_ctx->check_sat(hard.size(), hard.c_ptr()); + // bail out if hard constraints are not sat, or if there are no + // soft constraints + if (res != l_true || soft.empty()) {return res;} + + // the main loop + + model_ref mdl; + m_ctx->get_model(mdl); + + // don't proxy soft literals. Assume that they are propositional. + hard.append(soft); + soft.reset(); + + + // hard is divided into 4 regions + // x < hard_sz ---> hard constraints + // hard_sz <= x < i ---> sat soft constraints + // i <= x < j ---> backbones (unsat soft constraints) + // j <= x < hard.size() ---> unprocessed soft constraints + unsigned i, j; + i = hard_sz; + j = hard_sz; + + while (j < hard.size()) { + model_evaluator mev(*mdl); + + // move all true soft constraints to [hard_sz, i) + for (unsigned k = j; k < hard.size(); ++k) { + expr_ref e(m); + e = hard.get(k); + if (!mev.is_false(e) /* true or unset */) { + expr_ref tmp(m); + tmp = hard.get(i); + hard[i] = e; + if (i < j) { + // tmp is a backbone, put it at j + if (j == k) {hard[j] = tmp;} + else /* j < k */ { + e = hard.get(j); + hard[j] = tmp; + hard[k] = e; + } + j++; + } + else { + // there are no backbone literals + hard[k] = tmp; + j++; + } + i++; + } + } + + // done with the model. Reset to avoid confusion in debugging + mdl.reset(); + + // -- grow the set of backbone literals + for (;j < hard.size(); ++j) { + res = m_ctx->check_sat(j+1, hard.c_ptr()); + if (res == l_false) { + // -- flip non-true literal to be false + hard[j] = mk_not(m, hard.get(j)); + } + else if (res == l_true) { + // -- get the model for the next iteration of the outer loop + m_ctx->get_model(mdl); + break; + } + else if (res == l_undef) { + // -- conservatively bail out + hard.resize(hard_sz); + return l_undef; + } + } + } + + // move sat soft constraints to the output vector + for (unsigned k = i; k < j; ++k) { soft.push_back(hard.get(k)); } + // cleanup hard constraints + hard.resize(hard_sz); + return l_true; +} + /// Poor man's maxsat. No guarantees of maximum solution /// Runs maxsat loop on m_ctx Returns l_false if hard is unsat, /// otherwise reduces soft such that hard & soft is sat. -lbool prop_solver::maxsmt(expr_ref_vector &hard, expr_ref_vector &soft) +lbool prop_solver::maxsmt(expr_ref_vector &hard, expr_ref_vector &soft, + vector const & clauses) { // replace expressions by assumption literals - itp_solver::scoped_mk_proxy _p_(*m_ctx, hard); + iuc_solver::scoped_mk_proxy _p_(*m_ctx, hard); unsigned hard_sz = hard.size(); // assume soft constraints are propositional literals (no need to proxy) hard.append(soft); - lbool res = m_ctx->check_sat(hard.size(), hard.c_ptr()); + lbool res = m_ctx->check_sat_cc(hard, clauses); // if hard constraints alone are unsat or there are no soft // constraints, we are done if (res != l_false || soft.empty()) { return res; } @@ -146,7 +245,7 @@ lbool prop_solver::maxsmt(expr_ref_vector &hard, expr_ref_vector &soft) soft.reset(); expr_ref saved(m); - ptr_vector core; + expr_ref_vector core(m); m_ctx->get_unsat_core(core); // while there are soft constraints @@ -171,7 +270,7 @@ lbool prop_solver::maxsmt(expr_ref_vector &hard, expr_ref_vector &soft) } // check that the NEW constraints became sat - res = m_ctx->check_sat(hard.size(), hard.c_ptr()); + res = m_ctx->check_sat_cc(hard, clauses); if (res != l_false) { break; } // still unsat, update the core and repeat core.reset(); @@ -189,17 +288,21 @@ lbool prop_solver::maxsmt(expr_ref_vector &hard, expr_ref_vector &soft) return res; } -lbool prop_solver::internal_check_assumptions( - expr_ref_vector& hard_atoms, - expr_ref_vector& soft_atoms) +lbool prop_solver::internal_check_assumptions(expr_ref_vector &hard_atoms, + expr_ref_vector &soft_atoms, + vector const & clauses) { // XXX Turn model generation if m_model != 0 SASSERT(m_ctx); - SASSERT(m_ctx_fparams); - flet _model(m_ctx_fparams->m_model, m_model != nullptr); + + params_ref p; + if (m_model != nullptr) { + p.set_bool("produce_models", true); + m_ctx->updt_params(p); + } if (m_in_level) { assert_level_atoms(m_current_level); } - lbool result = maxsmt(hard_atoms, soft_atoms); + lbool result = maxsmt(hard_atoms, soft_atoms, clauses); if (result != l_false && m_model) { m_ctx->get_model(*m_model); } SASSERT(result != l_false || soft_atoms.empty()); @@ -226,15 +329,22 @@ lbool prop_solver::internal_check_assumptions( } if (result == l_false && m_core && m.proofs_enabled() && !m_subset_based_core) { - TRACE("spacer", tout << "theory core\n";); + TRACE("spacer", tout << "Using IUC core\n";); m_core->reset(); - m_ctx->get_itp_core(*m_core); + m_ctx->get_iuc(*m_core); } else if (result == l_false && m_core) { m_core->reset(); m_ctx->get_unsat_core(*m_core); // manually undo proxies because maxsmt() call above manually adds proxies + // AG: don't think this is needed. maxsmt() undoes the proxies already m_ctx->undo_proxies(*m_core); } + + if (m_model != nullptr) { + p.set_bool("produce_models", false); + m_ctx->updt_params(p); + } + return result; } @@ -242,9 +352,11 @@ lbool prop_solver::internal_check_assumptions( lbool prop_solver::check_assumptions(const expr_ref_vector & _hard, expr_ref_vector& soft, + const expr_ref_vector &clause, unsigned num_bg, expr * const * bg, unsigned solver_id) { + expr_ref cls(m); // current clients expect that flattening of HARD is // done implicitly during check_assumptions expr_ref_vector hard(m); @@ -252,12 +364,11 @@ lbool prop_solver::check_assumptions(const expr_ref_vector & _hard, flatten_and(hard); m_ctx = m_contexts [solver_id == 0 ? 0 : 0 /* 1 */].get(); - m_ctx_fparams = m_fparams [solver_id == 0 ? 0 : 0 /* 1 */]; // can be disabled if use_push_bg == true // solver::scoped_push _s_(*m_ctx); - if (!m_use_push_bg) { m_ctx->push(); } - itp_solver::scoped_bg _b_(*m_ctx); + if (!m_use_push_bg) {m_ctx->push();} + iuc_solver::scoped_bg _b_(*m_ctx); for (unsigned i = 0; i < num_bg; ++i) if (m_use_push_bg) { m_ctx->push_bg(bg [i]); } @@ -265,7 +376,9 @@ lbool prop_solver::check_assumptions(const expr_ref_vector & _hard, unsigned soft_sz = soft.size(); (void) soft_sz; - lbool res = internal_check_assumptions(hard, soft); + vector clauses; + if (!clause.empty()) clauses.push_back(clause); + lbool res = internal_check_assumptions(hard, soft, clauses); if (!m_use_push_bg) { m_ctx->pop(1); } TRACE("psolve_verbose", diff --git a/src/muz/spacer/spacer_prop_solver.h b/src/muz/spacer/spacer_prop_solver.h index 0cbcecfbf..1db65dc3e 100644 --- a/src/muz/spacer/spacer_prop_solver.h +++ b/src/muz/spacer/spacer_prop_solver.h @@ -29,25 +29,23 @@ Revision History: #include "smt/smt_kernel.h" #include "util/util.h" #include "util/vector.h" -#include "muz/spacer/spacer_manager.h" -#include "muz/spacer/spacer_smt_context_manager.h" -#include "muz/spacer/spacer_itp_solver.h" +#include "solver/solver.h" +#include "muz/spacer/spacer_iuc_solver.h" +#include "muz/spacer/spacer_util.h" -struct fixedpoint_params; +struct fp_params; namespace spacer { +typedef ptr_vector decl_vector; class prop_solver { private: ast_manager& m; - manager& m_pm; symbol m_name; - smt_params* m_fparams[2]; - solver* m_solvers[2]; - scoped_ptr m_contexts[2]; - itp_solver * m_ctx; - smt_params * m_ctx_fparams; + ref m_solvers[2]; + scoped_ptr m_contexts[2]; + iuc_solver * m_ctx; decl_vector m_level_preds; app_ref_vector m_pos_level_atoms; // atoms used to identify level app_ref_vector m_neg_level_atoms; // @@ -68,13 +66,17 @@ private: void ensure_level(unsigned lvl); lbool internal_check_assumptions(expr_ref_vector &hard, - expr_ref_vector &soft); + expr_ref_vector &soft, + vector const & clause); - lbool maxsmt(expr_ref_vector &hard, expr_ref_vector &soft); + lbool maxsmt(expr_ref_vector &hard, expr_ref_vector &soft, + vector const & clauses); + lbool mss(expr_ref_vector &hard, expr_ref_vector &soft); public: - prop_solver(spacer::manager& pm, fixedpoint_params const& p, symbol const& name); + prop_solver(ast_manager &m, solver *solver0, solver* solver1, + fp_params const& p, symbol const& name); void set_core(expr_ref_vector* core) { m_core = core; } @@ -91,11 +93,19 @@ public: void assert_expr(expr * form); void assert_expr(expr * form, unsigned level); + void assert_exprs(const expr_ref_vector &fmls) { + for (auto *f : fmls) assert_expr(f); + } + void assert_exprs(const expr_ref_vector &fmls, unsigned level) { + for (auto *f : fmls) assert_expr(f, level); + } + /** * check assumptions with a background formula */ lbool check_assumptions(const expr_ref_vector & hard, expr_ref_vector & soft, + const expr_ref_vector &clause, unsigned num_bg = 0, expr * const *bg = nullptr, unsigned solver_id = 0); @@ -136,7 +146,22 @@ public: ~scoped_delta_level() {m_delta = false;} }; + class scoped_weakness { + public: + solver *sol; + scoped_weakness(prop_solver &ps, unsigned solver_id, unsigned weakness) + : sol(nullptr) { + sol = ps.m_solvers[solver_id == 0 ? 0 : 0 /* 1 */].get(); + if (!sol) return; + sol->push_params(); + params_ref p; + p.set_bool("arith.ignore_int", weakness < 1); + p.set_bool("array.weak", weakness < 2); + sol->updt_params(p); + } + ~scoped_weakness() {if (sol) {sol->pop_params();}} + }; }; } diff --git a/src/muz/spacer/spacer_qe_project.cpp b/src/muz/spacer/spacer_qe_project.cpp index 753bb43b8..ed5a0215c 100644 --- a/src/muz/spacer/spacer_qe_project.cpp +++ b/src/muz/spacer/spacer_qe_project.cpp @@ -41,7 +41,7 @@ Revision History: #include "muz/spacer/spacer_mev_array.h" #include "muz/spacer/spacer_qe_project.h" -namespace +namespace spacer_qe { bool is_partial_eq (app* a); @@ -186,7 +186,7 @@ bool is_partial_eq (app* a) { } -namespace qe { +namespace spacer_qe { class is_relevant_default : public i_expr_pred { public: @@ -195,7 +195,7 @@ namespace qe { } }; - class mk_atom_default : public i_nnf_atom { + class mk_atom_default : public qe::i_nnf_atom { public: void operator()(expr* e, bool pol, expr_ref& result) override { if (pol) result = e; @@ -378,7 +378,7 @@ namespace qe { rational r; cx = mk_mul (c, m_var->x()); cxt = mk_add (cx, t); - VERIFY(mdl.eval(cxt, val, true)); + val = mdl(cxt); VERIFY(a.is_numeral(val, r)); SASSERT (r > rational::zero () || r < rational::zero ()); if (r > rational::zero ()) { @@ -464,8 +464,7 @@ namespace qe { m_strict.reset(); m_eq.reset (); - expr_ref var_val (m); - VERIFY (mdl.eval (m_var->x(), var_val, true)); + expr_ref var_val = mdl(m_var->x()); unsigned eq_idx = lits.size (); for (unsigned i = 0; i < lits.size(); ++i) { @@ -492,7 +491,7 @@ namespace qe { rational r; cx = mk_mul (c, m_var->x()); cxt = mk_add (cx, t); - VERIFY(mdl.eval(cxt, val, true)); + val = mdl(cxt); VERIFY(a.is_numeral(val, r)); if (is_eq) { @@ -738,7 +737,7 @@ namespace qe { rational r; cx = mk_mul (m_coeffs[max_t], m_var->x()); cxt = mk_add (cx, m_terms.get (max_t)); - VERIFY(mdl.eval(cxt, val, true)); + val = mdl(cxt); VERIFY(a.is_numeral(val, r)); // get the offset from the smallest/largest possible value for x @@ -796,13 +795,13 @@ namespace qe { // evaluate x in mdl rational r_x; - VERIFY(mdl.eval(m_var->x (), val, true)); + val = mdl(m_var->x ()); VERIFY(a.is_numeral (val, r_x)); for (unsigned i = 0; i < m_terms.size(); ++i) { rational const& ac = m_coeffs[i]; if (!m_eq[i] && ac.is_pos() == do_pos) { - VERIFY(mdl.eval(m_terms.get (i), val, true)); + val = mdl(m_terms.get (i)); VERIFY(a.is_numeral(val, r)); r /= abs(ac); // skip the literal if false in the model @@ -955,8 +954,7 @@ namespace qe { new_var = m.mk_fresh_const ("mod_var", d->get_range ()); eqs.push_back (m.mk_eq (new_var, new_term)); // obtain value of new_term in mdl - expr_ref val (m); - mdl.eval (new_term, val, true); + expr_ref val = mdl(new_term); // use the variable from now on new_term = new_var; changed = true; @@ -1413,7 +1411,7 @@ namespace qe { app_ref val_const (m.mk_fresh_const ("sel", val_sort), m); m_aux_vars.push_back (val_const); // extend M to include val_const - expr_ref val (m); + expr_ref val(m); m_mev.eval (*M, a_new, val); M->register_decl (val_const->get_decl (), val); // add equality @@ -2254,7 +2252,7 @@ namespace qe { void arith_project(model& mdl, app_ref_vector& vars, expr_ref& fml) { ast_manager& m = vars.get_manager(); arith_project_util ap(m); - atom_set pos_lits, neg_lits; + qe::atom_set pos_lits, neg_lits; is_relevant_default is_relevant; mk_atom_default mk_atom; get_nnf (fml, is_relevant, mk_atom, pos_lits, neg_lits); @@ -2264,7 +2262,7 @@ namespace qe { void arith_project(model& mdl, app_ref_vector& vars, expr_ref& fml, expr_map& map) { ast_manager& m = vars.get_manager(); arith_project_util ap(m); - atom_set pos_lits, neg_lits; + qe::atom_set pos_lits, neg_lits; is_relevant_default is_relevant; mk_atom_default mk_atom; get_nnf (fml, is_relevant, mk_atom, pos_lits, neg_lits); diff --git a/src/muz/spacer/spacer_qe_project.h b/src/muz/spacer/spacer_qe_project.h index b923dc3c7..65848f8f3 100644 --- a/src/muz/spacer/spacer_qe_project.h +++ b/src/muz/spacer/spacer_qe_project.h @@ -23,7 +23,7 @@ Notes: #include "model/model.h" #include "ast/expr_map.h" -namespace qe { +namespace spacer_qe { /** Loos-Weispfenning model-based projection for a basic conjunction. Lits is a vector of literals. diff --git a/src/muz/spacer/spacer_quant_generalizer.cpp b/src/muz/spacer/spacer_quant_generalizer.cpp new file mode 100644 index 000000000..f66fe7b29 --- /dev/null +++ b/src/muz/spacer/spacer_quant_generalizer.cpp @@ -0,0 +1,671 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_quant_generalizer.cpp + +Abstract: + + Quantified lemma generalizer. + +Author: + + + Yakir Vizel + Arie Gurfinkel + +Revision History: + +--*/ + + +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_generalizers.h" +#include "muz/spacer/spacer_manager.h" +#include "ast/expr_abstract.h" +#include "ast/rewriter/var_subst.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/factor_equivs.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "ast/substitution/matcher.h" +#include "ast/expr_functors.h" + +#include "muz/spacer/spacer_sem_matcher.h" + +using namespace spacer; + +namespace { +struct index_lt_proc : public std::binary_function { + arith_util m_arith; + index_lt_proc(ast_manager &m) : m_arith(m) {} + bool operator() (app *a, app *b) { + // XXX This order is a bit strange. + // XXX It does the job in our current application, but only because + // XXX we assume that we only compare expressions of the form (B + k), + // XXX where B is fixed and k is a number. + // XXX Might be better to specialize just for that specific use case. + rational val1, val2; + bool is_num1 = m_arith.is_numeral(a, val1); + bool is_num2 = m_arith.is_numeral(b, val2); + + if (is_num1 && is_num2) { + return val1 < val2; + } + else if (is_num1 != is_num2) { + return is_num1; + } + + is_num1 = false; + is_num2 = false; + // compare the first numeric argument of a to first numeric argument of b + // if available + for (unsigned i = 0, sz = a->get_num_args(); !is_num1 && i < sz; ++i) { + is_num1 = m_arith.is_numeral (a->get_arg(i), val1); + } + for (unsigned i = 0, sz = b->get_num_args(); !is_num2 && i < sz; ++i) { + is_num2 = m_arith.is_numeral(b->get_arg(i), val2); + } + + if (is_num1 && is_num2) { + return val1 < val2; + } + else if (is_num1 != is_num2) { + return is_num1; + } + else { + return a->get_id() < b->get_id(); + } + + } +}; + +} + +namespace spacer { + +lemma_quantifier_generalizer::lemma_quantifier_generalizer(context &ctx, + bool normalize_cube) : + lemma_generalizer(ctx), m(ctx.get_ast_manager()), m_arith(m), m_cube(m), + m_normalize_cube(normalize_cube), m_offset(0) {} + +void lemma_quantifier_generalizer::collect_statistics(statistics &st) const +{ + st.update("time.spacer.solve.reach.gen.quant", m_st.watch.get_seconds()); + st.update("quantifier gen", m_st.count); + st.update("quantifier gen failures", m_st.num_failures); +} + +/** + Finds candidates terms to be existentially abstracted. + A term t is a candidate if + (a) t is ground + + (b) t appears in an expression of the form (select A t) for some array A + + (c) t appears in an expression of the form (select A (+ t v)) + where v is ground + + The goal is to pick candidates that might result in a lemma in the + essentially uninterpreted fragment of FOL or its extensions. + */ +void lemma_quantifier_generalizer::find_candidates(expr *e, + app_ref_vector &candidates) { + if (!contains_selects(e, m)) return; + + app_ref_vector indices(m); + get_select_indices(e, indices, m); + + app_ref_vector extra(m); + expr_sparse_mark marked_args; + + // Make sure not to try and quantify already-quantified indices + for (unsigned idx=0, sz = indices.size(); idx < sz; idx++) { + // skip expressions that already contain a quantified variable + if (has_zk_const(indices.get(idx))) { + continue; + } + + app *index = indices.get(idx); + TRACE ("spacer_qgen", tout << "Candidate: "<< mk_pp(index, m) + << " in " << mk_pp(e, m) << "\n";); + extra.push_back(index); + if (m_arith.is_add(index)) { + for (expr * arg : *index) { + if (!is_app(arg) || marked_args.is_marked(arg)) {continue;} + marked_args.mark(arg); + candidates.push_back (to_app(arg)); + } + } + } + + std::sort(candidates.c_ptr(), candidates.c_ptr() + candidates.size(), + index_lt_proc(m)); + // keep actual select indecies in the order found at the back of + // candidate list. There is no particular reason for this order + candidates.append(extra); +} + + +/// returns true if expression e contains a sub-expression of the form (select A idx) where +/// idx contains exactly one skolem from zks. Returns idx and the skolem +bool lemma_quantifier_generalizer::match_sk_idx(expr *e, app_ref_vector const &zks, expr *&idx, app *&sk) { + if (zks.size() != 1) return false; + contains_app has_zk(m, zks.get(0)); + + if (!contains_selects(e, m)) return false; + app_ref_vector indicies(m); + get_select_indices(e, indicies, m); + if (indicies.size() > 2) return false; + + unsigned i=0; + if (indicies.size() == 1) { + if (!has_zk(indicies.get(0))) return false; + } + else { + if (has_zk(indicies.get(0)) && !has_zk(indicies.get(1))) + i = 0; + else if (!has_zk(indicies.get(0)) && has_zk(indicies.get(1))) + i = 1; + else if (!has_zk(indicies.get(0)) && !has_zk(indicies.get(1))) + return false; + } + + idx = indicies.get(i); + sk = zks.get(0); + return true; +} + +namespace { +expr* times_minus_one(expr *e, arith_util &arith) { + expr *r; + if (arith.is_times_minus_one (e, r)) { return r; } + + return arith.mk_mul(arith.mk_numeral(rational(-1), arith.is_int(get_sort(e))), e); +} +} + +/** Attempts to rewrite a cube so that quantified variable appears as + a top level argument of select-term + + Find sub-expression of the form (select A (+ sk!0 t)) and replaces + (+ sk!0 t) --> sk!0 and sk!0 --> (+ sk!0 (* (- 1) t)) + + Current implementation is an ugly hack for one special case +*/ +void lemma_quantifier_generalizer::cleanup(expr_ref_vector &cube, app_ref_vector const &zks, expr_ref &bind) { + if (zks.size() != 1) return; + + arith_util arith(m); + expr *idx = nullptr; + app *sk = nullptr; + expr_ref rep(m); + + for (expr *e : cube) { + if (match_sk_idx(e, zks, idx, sk)) { + CTRACE("spacer_qgen", idx != sk, + tout << "Possible cleanup of " << mk_pp(idx, m) << " in " + << mk_pp(e, m) << " on " << mk_pp(sk, m) << "\n";); + + if (!arith.is_add(idx)) continue; + app *a = to_app(idx); + bool found = false; + expr_ref_vector kids(m); + expr_ref_vector kids_bind(m); + for (expr* arg : *a) { + if (arg == sk) { + found = true; + kids.push_back(arg); + kids_bind.push_back(bind); + } + else { + kids.push_back (times_minus_one(arg, arith)); + kids_bind.push_back (times_minus_one(arg, arith)); + } + } + if (!found) continue; + + rep = arith.mk_add(kids.size(), kids.c_ptr()); + bind = arith.mk_add(kids_bind.size(), kids_bind.c_ptr()); + TRACE("spacer_qgen", + tout << "replace " << mk_pp(idx, m) << " with " << mk_pp(rep, m) << "\n";); + break; + } + } + + if (rep) { + expr_safe_replace rw(m); + rw.insert(sk, rep); + rw.insert(idx, sk); + rw(cube); + TRACE("spacer_qgen", + tout << "Cleaned cube to: " << mk_and(cube) << "\n";); + } +} + +/** + Create an abstract cube by abstracting a given term with a given variable. + On return, + gnd_cube contains all ground literals from m_cube + abs_cube contains all newly quantified literals from m_cube + lb contains an expression determining the lower bound on the variable + ub contains an expression determining the upper bound on the variable + + Conjunction of gnd_cube and abs_cube is the new quantified cube + + lb and ub are null if no bound was found + */ +void lemma_quantifier_generalizer::mk_abs_cube(lemma_ref &lemma, app *term, var *var, + expr_ref_vector &gnd_cube, + expr_ref_vector &abs_cube, + expr *&lb, expr *&ub, unsigned &stride) { + + // create an abstraction function that maps candidate term to variables + expr_safe_replace sub(m); + // term -> var + sub.insert(term , var); + rational val; + if (m_arith.is_numeral(term, val)) { + bool is_int = val.is_int(); + expr_ref minus_one(m); + minus_one = m_arith.mk_numeral(rational(-1), is_int); + + // term+1 -> var+1 if term is a number + sub.insert( + m_arith.mk_numeral(val + 1, is_int), + m_arith.mk_add(var, m_arith.mk_numeral(rational(1), is_int))); + // -term-1 -> -1*var + -1 if term is a number + sub.insert( + m_arith.mk_numeral(-1*val + -1, is_int), + m_arith.mk_add (m_arith.mk_mul (minus_one, var), minus_one)); + } + + lb = nullptr; + ub = nullptr; + + for (expr *lit : m_cube) { + expr_ref abs_lit(m); + sub (lit, abs_lit); + if (lit == abs_lit) { + gnd_cube.push_back(lit); + } + else { + expr *e1, *e2; + // generalize v=num into v>=num + if (m.is_eq(abs_lit, e1, e2) && (e1 == var || e2 == var)) { + if (m_arith.is_numeral(e1)) { + abs_lit = m_arith.mk_ge (var, e1); + } else if (m_arith.is_numeral(e2)) { + abs_lit = m_arith.mk_ge(var, e2); + } + } + abs_cube.push_back(abs_lit); + if (contains_selects(abs_lit, m)) { + expr_ref_vector pob_cube(m); + flatten_and(lemma->get_pob()->post(), pob_cube); + find_stride(pob_cube, abs_lit, stride); + } + + if (!lb && is_lb(var, abs_lit)) { + lb = abs_lit; + } + else if (!ub && is_ub(var, abs_lit)) { + ub = abs_lit; + } + } + } +} + +// -- returns true if e is an upper bound for var +bool lemma_quantifier_generalizer::is_ub(var *var, expr *e) { + expr *e1, *e2; + // var <= e2 + if ((m_arith.is_le (e, e1, e2) || m_arith.is_lt(e, e1, e2)) && var == e1) { + return true; + } + // e1 >= var + if ((m_arith.is_ge(e, e1, e2) || m_arith.is_gt(e, e1, e2)) && var == e2) { + return true; + } + + // t <= -1*var + if ((m_arith.is_le (e, e1, e2) || m_arith.is_lt(e, e1, e2)) + && m_arith.is_times_minus_one(e2, e2) && e2 == var) { + return true; + } + // -1*var >= t + if ((m_arith.is_ge(e, e1, e2) || m_arith.is_gt(e, e1, e2)) && + m_arith.is_times_minus_one(e1, e1) && e1 == var) { + return true; + } + // ! (var >= e2) + if (m.is_not (e, e1) && is_lb(var, e1)) { + return true; + } + // var + t1 <= t2 + if ((m_arith.is_le(e, e1, e2) || m_arith.is_lt(e, e1, e2)) && + m_arith.is_add(e1)) { + app *a = to_app(e1); + for (expr* arg : *a) { + if (arg == var) return true; + } + } + // t1 <= t2 + -1*var + if ((m_arith.is_le(e, e1, e2) || m_arith.is_lt(e, e1, e2)) && + m_arith.is_add(e2)) { + app *a = to_app(e2); + for (expr* arg : *a) { + if (m_arith.is_times_minus_one(arg, arg) && arg == var) + return true; + } + } + // t1 >= t2 + var + if ((m_arith.is_ge(e, e1, e2) || m_arith.is_gt(e, e1, e2)) && + m_arith.is_add(e2)) { + app *a = to_app(e2); + for (expr * arg : *a) { + if (arg == var) return true; + } + } + // -1*var + t1 >= t2 + if ((m_arith.is_ge(e, e1, e2) || m_arith.is_gt(e, e1, e2)) && + m_arith.is_add(e1)) { + app *a = to_app(e1); + for (expr * arg : *a) { + if (m_arith.is_times_minus_one(arg, arg) && arg == var) + return true; + } + } + return false; +} + +// -- returns true if e is a lower bound for var +bool lemma_quantifier_generalizer::is_lb(var *var, expr *e) { + expr *e1, *e2; + // var >= e2 + if ((m_arith.is_ge (e, e1, e2) || m_arith.is_gt(e, e1, e2)) && var == e1) { + return true; + } + // e1 <= var + if ((m_arith.is_le(e, e1, e2) || m_arith.is_lt(e, e1, e2)) && var == e2) { + return true; + } + // t >= -1*var + if ((m_arith.is_ge (e, e1, e2) || m_arith.is_gt(e, e1, e2)) + && m_arith.is_times_minus_one(e2, e2) && e2 == var) { + return true; + } + // -1*var <= t + if ((m_arith.is_le(e, e1, e2) || m_arith.is_lt(e, e1, e2)) && + m_arith.is_times_minus_one(e1, e1) && e1 == var) { + return true; + } + // ! (var <= e2) + if (m.is_not (e, e1) && is_ub(var, e1)) { + return true; + } + // var + t1 >= t2 + if ((m_arith.is_ge(e, e1, e2) || m_arith.is_gt(e, e1, e2)) && + m_arith.is_add(e1)) { + app *a = to_app(e1); + for (expr * arg : *a) { + if (arg == var) return true; + } + } + // t1 >= t2 + -1*var + if ((m_arith.is_ge(e, e1, e2) || m_arith.is_gt(e, e1, e2)) && + m_arith.is_add(e2)) { + app *a = to_app(e2); + for (expr * arg : *a) { + if (m_arith.is_times_minus_one(arg, arg) && arg == var) + return true; + } + } + // t1 <= t2 + var + if ((m_arith.is_le(e, e1, e2) || m_arith.is_lt(e, e1, e2)) && + m_arith.is_add(e2)) { + app *a = to_app(e2); + for (expr * arg : *a) { + if (arg == var) return true; + } + } + // -1*var + t1 <= t2 + if ((m_arith.is_le(e, e1, e2) || m_arith.is_lt(e, e1, e2)) && + m_arith.is_add(e1)) { + app *a = to_app(e1); + for (expr * arg : *a) { + if (m_arith.is_times_minus_one(arg, arg) && arg == var) + return true; + } + } + + return false; +} + +bool lemma_quantifier_generalizer::generalize (lemma_ref &lemma, app *term) { + + expr *lb = nullptr, *ub = nullptr; + unsigned stride = 1; + expr_ref_vector gnd_cube(m); + expr_ref_vector abs_cube(m); + + var_ref var(m); + var = m.mk_var (m_offset, get_sort(term)); + + mk_abs_cube(lemma, term, var, gnd_cube, abs_cube, lb, ub, stride); + if (abs_cube.empty()) {return false;} + + TRACE("spacer_qgen", + tout << "abs_cube is: " << mk_and(abs_cube) << "\n"; + tout << "lb = "; + if (lb) tout << mk_pp(lb, m); else tout << "none"; + tout << "\n"; + tout << "ub = "; + if (ub) tout << mk_pp(ub, m); else tout << "none"; + tout << "\n";); + + if (!lb && !ub) + return false; + + // -- guess lower or upper bound if missing + if (!lb) { + abs_cube.push_back (m_arith.mk_ge (var, term)); + lb = abs_cube.back(); + } + if (!ub) { + abs_cube.push_back (m_arith.mk_lt(var, term)); + ub = abs_cube.back(); + } + + rational init; + expr_ref constant(m); + if (is_var(to_app(lb)->get_arg(0))) + constant = to_app(lb)->get_arg(1); + else + constant = to_app(lb)->get_arg(0); + + if (stride > 1 && m_arith.is_numeral(constant, init)) { + unsigned mod = init.get_unsigned() % stride; + TRACE("spacer_qgen", + tout << "mod=" << mod << " init=" << init << " stride=" << stride << "\n"; + tout.flush();); + abs_cube.push_back(m.mk_eq( + m_arith.mk_mod(var, m_arith.mk_numeral(rational(stride), true)), + m_arith.mk_numeral(rational(mod), true))); + } + + // skolemize + expr_ref gnd(m); + app_ref_vector zks(m); + ground_expr(mk_and(abs_cube), gnd, zks); + flatten_and(gnd, gnd_cube); + + TRACE("spacer_qgen", + tout << "New CUBE is: " << gnd_cube << "\n";); + + // check if the result is a true lemma + unsigned uses_level = 0; + pred_transformer &pt = lemma->get_pob()->pt(); + if (pt.check_inductive(lemma->level(), gnd_cube, uses_level, lemma->weakness())) { + TRACE("spacer_qgen", + tout << "Quantifier Generalization Succeeded!\n" + << "New CUBE is: " << gnd_cube << "\n";); + SASSERT(zks.size() >= static_cast(m_offset)); + + // lift quantified variables to top of select + expr_ref ext_bind(m); + ext_bind = term; + cleanup(gnd_cube, zks, ext_bind); + + // XXX better do that check before changing bind in cleanup() + // XXX Or not because substitution might introduce _n variable into bind + if (m_ctx.get_manager().is_n_formula(ext_bind)) { + // XXX this creates an instance, but not necessarily the needed one + + // XXX This is sound because any instance of + // XXX universal quantifier is sound + + // XXX needs better long term solution. leave + // comment here for the future + m_ctx.get_manager().formula_n2o(ext_bind, ext_bind, 0); + } + + lemma->update_cube(lemma->get_pob(), gnd_cube); + lemma->set_level(uses_level); + + SASSERT(var->get_idx() < zks.size()); + SASSERT(is_app(ext_bind)); + lemma->add_skolem(zks.get(var->get_idx()), to_app(ext_bind)); + return true; + } + + return false; +} + +bool lemma_quantifier_generalizer::find_stride(expr_ref_vector &c, expr_ref &pattern, unsigned &stride) { + expr_ref tmp(m); + tmp = mk_and(c); + normalize(tmp, tmp, false, true); + c.reset(); + flatten_and(tmp, c); + + app_ref_vector indices(m); + get_select_indices(pattern, indices, m); + + // TODO + if (indices.size() > 1) + return false; + + app *p_index = indices.get(0); + if (is_var(p_index)) return false; + + std::vector instances; + for (expr* lit : c) { + + if (!contains_selects(lit, m)) + continue; + + indices.reset(); + get_select_indices(lit, indices, m); + + // TODO: + if (indices.size() > 1) + continue; + + app *candidate = indices.get(0); + + unsigned size = p_index->get_num_args(); + unsigned matched = 0; + for (unsigned p=0; p < size; p++) { + expr *arg = p_index->get_arg(p); + if (is_var(arg)) { + rational val; + if (p < candidate->get_num_args() && + m_arith.is_numeral(candidate->get_arg(p), val) && + val.is_unsigned()) { + instances.push_back(val.get_unsigned()); + } + } + else { + for (expr* cand : *candidate) { + if (cand == arg) { + matched++; + break; + } + } + } + } + + if (matched < size - 1) + continue; + + if (candidate->get_num_args() == matched) + instances.push_back(0); + + TRACE("spacer_qgen", + tout << "Match succeeded!\n";); + } + + if (instances.size() <= 1) + return false; + + std::sort(instances.begin(), instances.end()); + + stride = instances[1]-instances[0]; + TRACE("spacer_qgen", tout << "Index Stride is: " << stride << "\n";); + + return true; +} + +void lemma_quantifier_generalizer::operator()(lemma_ref &lemma) { + if (lemma->get_cube().empty()) return; + if (!lemma->has_pob()) return; + + m_st.count++; + scoped_watch _w_(m_st.watch); + + TRACE("spacer_qgen", + tout << "initial cube: " << mk_and(lemma->get_cube()) << "\n";); + + // setup the cube + m_cube.reset(); + m_cube.append(lemma->get_cube()); + + if (m_normalize_cube) { + // -- re-normalize the cube + expr_ref c(m); + c = mk_and(m_cube); + normalize(c, c, false, true); + m_cube.reset(); + flatten_and(c, m_cube); + TRACE("spacer_qgen", + tout << "normalized cube:\n" << mk_and(m_cube) << "\n";); + } + + // first unused free variable + m_offset = lemma->get_pob()->get_free_vars_size(); + + // for every literal, find a candidate term to abstract + for (unsigned i=0; i < m_cube.size(); i++) { + expr *r = m_cube.get(i); + + // generate candidates for abstraction + app_ref_vector candidates(m); + find_candidates(r, candidates); + if (candidates.empty()) continue; + + // for every candidate + for (unsigned arg=0, sz = candidates.size(); arg < sz; arg++) { + if (generalize (lemma, candidates.get(arg))) { + return; + } + else { + ++m_st.num_failures; + } + } + } +} + + + +} diff --git a/src/muz/spacer/spacer_sat_answer.cpp b/src/muz/spacer/spacer_sat_answer.cpp new file mode 100644 index 000000000..30241230f --- /dev/null +++ b/src/muz/spacer/spacer_sat_answer.cpp @@ -0,0 +1,181 @@ +#include "muz/spacer/spacer_sat_answer.h" +#include "muz/base/dl_context.h" +#include "muz/base/dl_rule.h" + +#include "smt/smt_solver.h" + +namespace spacer { + +struct ground_sat_answer_op::frame { + reach_fact *m_rf; + pred_transformer &m_pt; + expr_ref_vector m_gnd_subst; + expr_ref m_gnd_eq; + expr_ref m_fact; + unsigned m_visit; + expr_ref_vector m_kids; + + frame(reach_fact *rf, pred_transformer &pt, const expr_ref_vector &gnd_subst) : + m_rf(rf), m_pt(pt), + m_gnd_subst(gnd_subst), + m_gnd_eq(pt.get_ast_manager()), + m_fact(pt.get_ast_manager()), + m_visit(0), + m_kids(pt.get_ast_manager()) { + + ast_manager &m = pt.get_ast_manager(); + spacer::manager &pm = pt.get_manager(); + + m_fact = m.mk_app(head(), m_gnd_subst.size(), m_gnd_subst.c_ptr()); + if (pt.head()->get_arity() == 0) + m_gnd_eq = m.mk_true(); + else { + SASSERT(m_gnd_subst.size() == pt.head()->get_arity()); + for (unsigned i = 0, sz = pt.sig_size(); i < sz; ++i) { + m_gnd_eq = m.mk_eq(m.mk_const(pm.o2n(pt.sig(i), 0)), + m_gnd_subst.get(i)); + } + } + } + + func_decl* head() {return m_pt.head();} + expr* fact() {return m_fact;} + const datalog::rule &rule() {return m_rf->get_rule();} + pred_transformer &pt() {return m_pt;} +}; + +ground_sat_answer_op::ground_sat_answer_op(context &ctx) : + m_ctx(ctx), m(m_ctx.get_ast_manager()), m_pm(m_ctx.get_manager()), + m_pinned(m) { + m_solver = mk_smt_solver(m, params_ref::get_empty(), symbol::null); +} + +proof_ref ground_sat_answer_op::operator()(pred_transformer &query) { + + + vector todo, new_todo; + + // -- find substitution for a query if query is not nullary + expr_ref_vector qsubst(m); + if (query.head()->get_arity() > 0) { + solver::scoped_push _s_(*m_solver); + m_solver->assert_expr(query.get_last_rf()->get()); + lbool res = m_solver->check_sat(0, nullptr); + (void)res; + SASSERT(res == l_true); + model_ref mdl; + m_solver->get_model(mdl); + model::scoped_model_completion _scm(mdl, true); + for (unsigned i = 0, sz = query.sig_size(); i < sz; ++i) { + expr_ref arg(m), val(m); + arg = m.mk_const(m_pm.o2n(query.sig(i), 0)); + val = (*mdl)(arg); + qsubst.push_back(val); + } + } + + todo.push_back(frame(query.get_last_rf(), query, qsubst)); + expr_ref root_fact(m); + root_fact = todo.back().fact(); + + while (!todo.empty()) { + frame &curr = todo.back(); + if (m_cache.contains(curr.fact())) { + todo.pop_back(); + continue; + } + + if (curr.m_visit == 0) { + new_todo.reset(); + mk_children(curr, new_todo); + curr.m_visit = 1; + // curr becomes invalid + todo.append(new_todo); + } + else { + proof* pf = mk_proof_step(curr); + m_pinned.push_back(curr.fact()); + m_cache.insert(curr.fact(), pf); + todo.pop_back(); + } + } + return proof_ref(m_cache.find(root_fact), m); +} + + +void ground_sat_answer_op::mk_children(frame &fr, vector &todo) { + const datalog::rule &r = fr.rule(); + ptr_vector preds; + fr.pt().find_predecessors(r, preds); + + if (preds.empty()) return; + + const reach_fact_ref_vector &kid_rfs = fr.m_rf->get_justifications(); + solver::scoped_push _s_(*m_solver); + m_solver->assert_expr(fr.m_gnd_eq); + unsigned ut_sz = r.get_uninterpreted_tail_size(); + for (unsigned i = 0; i < ut_sz; ++i) { + expr_ref f(m); + m_pm.formula_n2o(kid_rfs.get(i)->get(), f, i); + m_solver->assert_expr(f); + } + m_solver->assert_expr(fr.pt().transition()); + m_solver->assert_expr(fr.pt().rule2tag(&r)); + + lbool res = m_solver->check_sat(0, nullptr); + (void)res; + VERIFY(res == l_true); + + model_ref mdl; + m_solver->get_model(mdl); + expr_ref_vector subst(m); + for (unsigned i = 0, sz = preds.size(); i < sz; ++i) { + subst.reset(); + mk_child_subst_from_model(preds.get(i), i, mdl, subst); + todo.push_back(frame(kid_rfs.get(i), + m_ctx.get_pred_transformer(preds.get(i)), subst)); + fr.m_kids.push_back(todo.back().fact()); + } +} + + +void ground_sat_answer_op::mk_child_subst_from_model(func_decl *pred, + unsigned j, model_ref &mdl, + expr_ref_vector &subst) { + + model::scoped_model_completion _scm(mdl, true); + pred_transformer &pt = m_ctx.get_pred_transformer(pred); + for (unsigned i = 0, sz = pt.sig_size(); i < sz; ++i) { + expr_ref arg(m), val(m); + arg = m.mk_const(m_pm.o2o(pt.sig(i), 0, j)); + val = (*mdl)(arg); + subst.push_back(val); + } +} + +proof *ground_sat_answer_op::mk_proof_step(frame &fr) { + svector> positions; + vector substs; + + proof_ref_vector premises(m); + datalog::rule_manager &rm = m_ctx.get_datalog_context().get_rule_manager(); + expr_ref rule_fml(m); + rm.to_formula(fr.rule(), rule_fml); + // premises.push_back(fr.rule().get_proof()); + premises.push_back(m.mk_asserted(rule_fml)); + for (auto &k : fr.m_kids) {premises.push_back(m_cache.find(k));} + + for (unsigned i = 0; i < premises.size(); i++) { + positions.push_back(std::make_pair(0,i)); + } + for (unsigned i = 0; i <= premises.size(); i++) { + substs.push_back(expr_ref_vector(m)); + } + m_pinned.push_back(m.mk_hyper_resolve(premises.size(), + premises.c_ptr(), + fr.fact(), + positions, substs)); + return to_app(m_pinned.back()); +} + +} diff --git a/src/muz/spacer/spacer_sat_answer.h b/src/muz/spacer/spacer_sat_answer.h new file mode 100644 index 000000000..6cfd3b14c --- /dev/null +++ b/src/muz/spacer/spacer_sat_answer.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2018 Arie Gurfinkel + +Module Name: + + spacer_sat_answer.h + +Abstract: + + Compute refutation proof for CHC + +Author: + + Arie Gurfinkel + +Revision History: + +--*/ + +#ifndef _SPACER_SAT_ANSWER_H_ +#define _SPACER_SAT_ANSWER_H_ + +#include "muz/spacer/spacer_context.h" +#include "ast/ast.h" +#include "util/obj_hashtable.h" +#include "model/model.h" +#include "solver/solver.h" + +namespace spacer { + +class ground_sat_answer_op { + context &m_ctx; + ast_manager &m; + manager &m_pm; + + expr_ref_vector m_pinned; + obj_map m_cache; + + ref m_solver; + + struct frame; + + proof *mk_proof_step(frame &fr); + void mk_children(frame &fr, vector &todo); + void mk_child_subst_from_model(func_decl *pred, unsigned i, + model_ref &mdl, expr_ref_vector &subst); + +public: + ground_sat_answer_op(context &ctx); + + proof_ref operator() (pred_transformer &query); +}; +} + +#endif diff --git a/src/muz/spacer/spacer_sem_matcher.cpp b/src/muz/spacer/spacer_sem_matcher.cpp new file mode 100644 index 000000000..f3c2af8a9 --- /dev/null +++ b/src/muz/spacer/spacer_sem_matcher.cpp @@ -0,0 +1,147 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + sem_matcher.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + Arie Gurfinkel + +Revision History: + +--*/ +#include "muz/spacer/spacer_sem_matcher.h" + +namespace spacer { + +sem_matcher::sem_matcher(ast_manager &man) : m(man), m_arith(m), m_pinned(m) {} + +bool sem_matcher::match_var (var *v, expr *e) { + expr_offset r; + if (m_subst->find(v, 0, r)) { + if (!m.are_equal(r.get_expr(), e)) { + return false; + } + } + else { + m_subst->insert(v, 0, expr_offset(e, 1)); + } + return true; +} +bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos) { + reset(); + m_subst = &s; + m_todo.push_back(expr_pair(e1, e2)); + + // true on the first run through the loop + bool top = true; + pos = true; + while (!m_todo.empty()) { + expr_pair const & p = m_todo.back(); + + if (is_var(p.first)) { + if (!match_var(to_var(p.first), p.second)) { + return false; + } + m_todo.pop_back(); + top = false; + continue; + } + + + if (is_var(p.second)) + return false; + if (!is_app(p.first)) + return false; + if (!is_app(p.second)) + return false; + + app * n1 = to_app(p.first); + app * n2 = to_app(p.second); + + expr *t = nullptr; + + // strip negation + if (top && n1->get_decl() != n2->get_decl()) { + if (m.is_not(n1, t) && !m.is_not(n2) && is_app(t) && + to_app(t)->get_decl() == n2->get_decl()) { + pos = false; + n1 = to_app(e1); + } + else if (!m.is_not(n1) && m.is_not(n2, t) && is_app(t) && + to_app(t)->get_decl() == n1->get_decl()) { + pos = false; + n2 = to_app(t); + } + } + top = false; + + if (n1->get_decl() != n2->get_decl()) { + expr *e1 = nullptr, *e2 = nullptr; + rational val1, val2; + + // x<=y == !(x>y) + if (m_arith.is_le(n1) && m.is_not(n2, t) && m_arith.is_gt(t)) { + n2 = to_app(t); + } + else if (m_arith.is_le(n2) && m.is_not(n1, t) && m_arith.is_gt(t)) { + n1 = to_app(t); + } + // x>=y == !(xget_num_args(); + if (num_args1 != n2->get_num_args()) + return false; + + m_todo.pop_back(); + + if (num_args1 == 0) + continue; + + unsigned j = num_args1; + while (j > 0) { + --j; + m_todo.push_back(expr_pair(n1->get_arg(j), n2->get_arg(j))); + } + } + return true; +} + +void sem_matcher::reset() { + m_todo.reset(); + m_pinned.reset(); +} +} diff --git a/src/muz/spacer/spacer_sem_matcher.h b/src/muz/spacer/spacer_sem_matcher.h new file mode 100644 index 000000000..7d169166e --- /dev/null +++ b/src/muz/spacer/spacer_sem_matcher.h @@ -0,0 +1,69 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + sem_matcher.h + +Abstract: + + Semantic matcher + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + Arie Gurfinkel + +Revision History: + +--*/ +#ifndef SPACER_SEM_MATCHER_H_ +#define SPACER_SEM_MATCHER_H_ + +#include "ast/substitution/substitution.h" +#include "ast/arith_decl_plugin.h" +#include "util/hashtable.h" + +namespace spacer { +/** + \brief Functor for matching expressions. +*/ +class sem_matcher { + typedef std::pair expr_pair; + typedef pair_hash, obj_ptr_hash > expr_pair_hash; + typedef hashtable > cache; + + ast_manager &m; + arith_util m_arith; + expr_ref_vector m_pinned; + substitution * m_subst; + svector m_todo; + + void reset(); + + bool match_var(var *v, expr *e); +public: + sem_matcher(ast_manager &man); + + /** + \brief Return true if e2 is an instance of e1. + In case of success (result is true), it will store the substitution that makes e1 equals to e2 into s. + Sets pos to true if the match is positive and to false if it is negative (i.e., e1 equals !e2) + + For example: + 1) e1 = f(g(x), x), e2 = f(g(h(a)), h(a)) + The result is true, and s will contain x -> h(a) + + 2) e1 = f(a, x) e2 = f(x, a) + The result is false. + + 3) e1 = f(x, x) e2 = f(y, a) + The result is false + + 4) e1 = f(x, y) e2 = f(h(z), a) + The result is true, and s contains x->h(z) and y->a + */ + bool operator()(expr * e1, expr * e2, substitution & s, bool &pos); +}; +} +#endif /* SPACER_SEM_MATCHER_H_ */ diff --git a/src/muz/spacer/spacer_smt_context_manager.cpp b/src/muz/spacer/spacer_smt_context_manager.cpp deleted file mode 100644 index e26381afd..000000000 --- a/src/muz/spacer/spacer_smt_context_manager.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - spacer_smt_context_manager.cpp - -Abstract: - - Manager of smt contexts - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-26. - Arie Gurfinkel -Revision History: - ---*/ - - -#include "ast/ast_pp.h" -#include "ast/ast_pp_util.h" -#include "ast/ast_smt_pp.h" - -#include "smt/smt_context.h" -#include "smt/params/smt_params.h" - -#include "muz/spacer/spacer_util.h" -#include "muz/spacer/spacer_smt_context_manager.h" -namespace spacer { - - - - -smt_context_manager::smt_context_manager(ast_manager &m, - unsigned max_num_contexts, - const params_ref &p) : - m_fparams(p), - m(m), - m_max_num_contexts(max_num_contexts), - m_num_contexts(0) { m_stats.reset();} - - -smt_context_manager::~smt_context_manager() -{ - std::for_each(m_solvers.begin(), m_solvers.end(), - delete_proc()); -} - -virtual_solver* smt_context_manager::mk_fresh() -{ - ++m_num_contexts; - virtual_solver_factory *solver_factory = nullptr; - - if (m_max_num_contexts == 0 || m_solvers.size() < m_max_num_contexts) { - m_solvers.push_back(alloc(spacer::virtual_solver_factory, m, m_fparams)); - solver_factory = m_solvers.back(); - } else - { solver_factory = m_solvers[(m_num_contexts - 1) % m_max_num_contexts]; } - - return solver_factory->mk_solver(); -} - -void smt_context_manager::collect_statistics(statistics& st) const -{ - for (unsigned i = 0; i < m_solvers.size(); ++i) { - m_solvers[i]->collect_statistics(st); - } -} - -void smt_context_manager::reset_statistics() -{ - for (unsigned i = 0; i < m_solvers.size(); ++i) { - m_solvers[i]->reset_statistics(); - } -} - - -}; diff --git a/src/muz/spacer/spacer_smt_context_manager.h b/src/muz/spacer/spacer_smt_context_manager.h deleted file mode 100644 index 0df9bc77b..000000000 --- a/src/muz/spacer/spacer_smt_context_manager.h +++ /dev/null @@ -1,68 +0,0 @@ -/*++ -Copyright (c) 2011 Microsoft Corporation - -Module Name: - - spacer_smt_context_manager.h - -Abstract: - - Manager of smt contexts - -Author: - - Nikolaj Bjorner (nbjorner) 2011-11-26. - Arie Gurfinkel -Revision History: - ---*/ - -#ifndef _SPACER_SMT_CONTEXT_MANAGER_H_ -#define _SPACER_SMT_CONTEXT_MANAGER_H_ - -#include "util/stopwatch.h" - -#include "smt/smt_kernel.h" -#include "muz/base/dl_util.h" -#include "muz/spacer/spacer_virtual_solver.h" - -namespace spacer { - -class smt_context_manager { - - struct stats { - unsigned m_num_smt_checks; - unsigned m_num_sat_smt_checks; - stats() { reset(); } - void reset() { memset(this, 0, sizeof(*this)); } - }; - - smt_params m_fparams; - ast_manager& m; - unsigned m_max_num_contexts; - ptr_vector m_solvers; - unsigned m_num_contexts; - - - stats m_stats; - stopwatch m_check_watch; - stopwatch m_check_sat_watch; - -public: - smt_context_manager(ast_manager& m, unsigned max_num_contexts = 1, - const params_ref &p = params_ref::get_empty()); - - ~smt_context_manager(); - virtual_solver* mk_fresh(); - - void collect_statistics(statistics& st) const; - void reset_statistics(); - - void updt_params(params_ref const &p) { m_fparams.updt_params(p); } - smt_params& fparams() {return m_fparams;} - -}; - -}; - -#endif diff --git a/src/muz/spacer/spacer_sym_mux.cpp b/src/muz/spacer/spacer_sym_mux.cpp index ee1d3726d..cfe0908d6 100644 --- a/src/muz/spacer/spacer_sym_mux.cpp +++ b/src/muz/spacer/spacer_sym_mux.cpp @@ -1,5 +1,5 @@ /*++ -Copyright (c) 2011 Microsoft Corporation +Copyright (c) 2018 Arie Gurfinkel and Microsoft Corporation Module Name: @@ -7,10 +7,12 @@ Module Name: Abstract: - A symbol multiplexer that helps with having multiple versions of each of a set of symbols. + A symbol multiplexer that helps with having multiple versions of + each of a set of symbols. Author: + Arie Gurfinkel Krystof Hoder (t-khoder) 2011-9-8. Revision History: @@ -22,345 +24,123 @@ Revision History: #include "ast/rewriter/rewriter.h" #include "ast/rewriter/rewriter_def.h" -#include "model/model.h" - #include "muz/spacer/spacer_util.h" #include "muz/spacer/spacer_sym_mux.h" using namespace spacer; -sym_mux::sym_mux(ast_manager & m, const std::vector & suffixes) - : m(m), m_ref_holder(m), m_next_sym_suffix_idx(0), m_suffixes(suffixes) -{ - for (std::string const& s : m_suffixes) { - m_used_suffixes.insert(symbol(s.c_str())); +sym_mux::sym_mux(ast_manager & m) : m(m) {} +sym_mux::~sym_mux() { + for (auto &entry : m_entries) { + dealloc(entry.m_value); } } -std::string sym_mux::get_suffix(unsigned i) const -{ - while (m_suffixes.size() <= i) { - std::string new_suffix; - symbol new_syffix_sym; - do { - std::stringstream stm; - stm << '_' << m_next_sym_suffix_idx; - m_next_sym_suffix_idx++; - new_suffix = stm.str(); - new_syffix_sym = symbol(new_suffix.c_str()); - } while (m_used_suffixes.contains(new_syffix_sym)); - m_used_suffixes.insert(new_syffix_sym); - m_suffixes.push_back(new_suffix); - } - return m_suffixes[i]; +func_decl_ref sym_mux::mk_variant(func_decl *fdecl, unsigned i) const { + func_decl_ref v(m); + std::string name = fdecl->get_name().str(); + std::string suffix = "_"; + suffix += i == 0 ? "n" : std::to_string(i - 1); + name += suffix; + v = m.mk_func_decl(symbol(name.c_str()), fdecl->get_arity(), + fdecl->get_domain(), fdecl->get_range()); + return v; } -void sym_mux::create_tuple(func_decl* prefix, unsigned arity, sort * const * domain, sort * range, - unsigned tuple_length, decl_vector & tuple) -{ - SASSERT(tuple_length > 0); - while (tuple.size() < tuple_length) { - tuple.push_back(0); - } - SASSERT(tuple.size() == tuple_length); - std::string pre = prefix->get_name().str(); - for (unsigned i = 0; i < tuple_length; i++) { +void sym_mux::register_decl(func_decl *fdecl) { + sym_mux_entry *entry = alloc(sym_mux_entry, m); + entry->m_main = fdecl; + entry->m_variants.push_back(mk_variant(fdecl, 0)); + entry->m_variants.push_back(mk_variant(fdecl, 1)); - if (tuple[i] != 0) { - SASSERT(tuple[i]->get_arity() == arity); - SASSERT(tuple[i]->get_range() == range); - //domain should match as well, but we won't bother checking an array equality - } else { - std::string name = pre + get_suffix(i); - tuple[i] = m.mk_func_decl(symbol(name.c_str()), arity, domain, range); - } - m_ref_holder.push_back(tuple[i]); - m_sym2idx.insert(tuple[i], i); - m_sym2prim.insert(tuple[i], tuple[0]); - } - - m_prim2all.insert(tuple[0], tuple); - m_prefix2prim.insert(prefix, tuple[0]); - m_prim2prefix.insert(tuple[0], prefix); - m_prim_preds.push_back(tuple[0]); - m_ref_holder.push_back(prefix); + m_entries.insert(fdecl, entry); + m_muxes.insert(entry->m_variants.get(0), std::make_pair(entry, 0)); + m_muxes.insert(entry->m_variants.get(1), std::make_pair(entry, 1)); } - -void sym_mux::ensure_tuple_size(func_decl * prim, unsigned sz) const -{ - SASSERT(m_prim2all.contains(prim)); - decl_vector& tuple = m_prim2all.find_core(prim)->get_data().m_value; - SASSERT(tuple[0] == prim); - - if (sz <= tuple.size()) { return; } - - func_decl * prefix; - TRUSTME(m_prim2prefix.find(prim, prefix)); - std::string prefix_name = prefix->get_name().bare_str(); - for (unsigned i = tuple.size(); i < sz; ++i) { - std::string name = prefix_name + get_suffix(i); - func_decl * new_sym = m.mk_func_decl(symbol(name.c_str()), prefix->get_arity(), - prefix->get_domain(), prefix->get_range()); - - tuple.push_back(new_sym); - m_ref_holder.push_back(new_sym); - m_sym2idx.insert(new_sym, i); - m_sym2prim.insert(new_sym, prim); +void sym_mux::ensure_capacity(sym_mux_entry &entry, unsigned sz) const { + while (entry.m_variants.size() < sz) { + unsigned idx = entry.m_variants.size(); + entry.m_variants.push_back (mk_variant(entry.m_main, idx)); + m_muxes.insert(entry.m_variants.back(), std::make_pair(&entry, idx)); } } -func_decl * sym_mux::conv(func_decl * sym, unsigned src_idx, unsigned tgt_idx) const -{ - if (src_idx == tgt_idx) { return sym; } - func_decl * prim = (src_idx == 0) ? sym : get_primary(sym); - if (tgt_idx > src_idx) { - ensure_tuple_size(prim, tgt_idx + 1); - } - decl_vector & sym_vect = m_prim2all.find_core(prim)->get_data().m_value; - SASSERT(sym_vect[src_idx] == sym); - return sym_vect[tgt_idx]; +bool sym_mux::find_idx(func_decl * sym, unsigned & idx) const { + std::pair entry; + if (m_muxes.find(sym, entry)) {idx = entry.second; return true;} + return false; } - -func_decl * sym_mux::get_or_create_symbol_by_prefix(func_decl* prefix, unsigned idx, - unsigned arity, sort * const * domain, sort * range) -{ - func_decl * prim = try_get_primary_by_prefix(prefix); - if (prim) { - SASSERT(prim->get_arity() == arity); - SASSERT(prim->get_range() == range); - //domain should match as well, but we won't bother checking an array equality - - return conv(prim, 0, idx); +func_decl * sym_mux::find_by_decl(func_decl* fdecl, unsigned idx) const { + sym_mux_entry *entry = nullptr; + if (m_entries.find(fdecl, entry)) { + ensure_capacity(*entry, idx+1); + return entry->m_variants.get(idx); } - - decl_vector syms; - create_tuple(prefix, arity, domain, range, idx + 1, syms); - return syms[idx]; + return nullptr; } -bool sym_mux::is_muxed_lit(expr * e, unsigned idx) const -{ - if (!is_app(e)) { return false; } - app * a = to_app(e); - if (m.is_not(a) && is_app(a->get_arg(0))) { - a = to_app(a->get_arg(0)); +func_decl * sym_mux::shift_decl(func_decl * decl, + unsigned src_idx, unsigned tgt_idx) const { + std::pair entry; + if (m_muxes.find(decl, entry)) { + SASSERT(entry.second == src_idx); + ensure_capacity(*entry.first, tgt_idx + 1); + return entry.first->m_variants.get(tgt_idx); } - return is_muxed(a->get_decl()); + UNREACHABLE(); + return nullptr; } +namespace { +struct formula_checker { + formula_checker(const sym_mux & parent, unsigned idx) : + m_parent(parent), m_idx(idx), m_found(false) {} -struct sym_mux::formula_checker { - formula_checker(const sym_mux & parent, bool all, unsigned idx) : - m_parent(parent), m_all(all), m_idx(idx), - m_found_what_needed(false) - { - } - - void operator()(expr * e) - { - if (m_found_what_needed || !is_app(e)) { return; } + void operator()(expr * e) { + if (m_found || !is_app(e)) { return; } func_decl * sym = to_app(e)->get_decl(); unsigned sym_idx; - if (!m_parent.try_get_index(sym, sym_idx)) { return; } + if (!m_parent.find_idx(sym, sym_idx)) { return; } bool have_idx = sym_idx == m_idx; - - if (m_all ? (!have_idx) : have_idx) { - m_found_what_needed = true; - } - + m_found = !have_idx; } - bool all_have_idx() const - { - SASSERT(m_all); //we were looking for the queried property - return !m_found_what_needed; - } - - bool some_with_idx() const - { - SASSERT(!m_all); //we were looking for the queried property - return m_found_what_needed; - } + bool all_have_idx() const {return !m_found;} private: const sym_mux & m_parent; - bool m_all; unsigned m_idx; - - /** - If we check whether all muxed symbols are of given index, we look for - counter-examples, checking whether form contains a muxed symbol of an index, - we look for symbol of index m_idx. - */ - bool m_found_what_needed; + bool m_found; }; - -bool sym_mux::contains(expr * e, unsigned idx) const -{ - formula_checker chck(*this, false, idx); - for_each_expr(chck, m_visited, e); - m_visited.reset(); - return chck.some_with_idx(); } -bool sym_mux::is_homogenous_formula(expr * e, unsigned idx) const -{ - formula_checker chck(*this, true, idx); - for_each_expr(chck, m_visited, e); - m_visited.reset(); - return chck.all_have_idx(); +bool sym_mux::is_homogenous_formula(expr * e, unsigned idx) const { + expr_mark visited; + formula_checker fck(*this, idx); + for_each_expr(fck, visited, e); + return fck.all_have_idx(); } -bool sym_mux::is_homogenous(const expr_ref_vector & vect, unsigned idx) const -{ - expr * const * begin = vect.c_ptr(); - expr * const * end = begin + vect.size(); - for (expr * const * it = begin; it != end; it++) { - if (!is_homogenous_formula(*it, idx)) { - return false; - } - } - return true; -} - -class sym_mux::index_collector { - sym_mux const& m_parent; - svector m_indices; -public: - index_collector(sym_mux const& s): - m_parent(s) {} - - void operator()(expr * e) - { - if (is_app(e)) { - func_decl * sym = to_app(e)->get_decl(); - unsigned idx; - if (m_parent.try_get_index(sym, idx)) { - SASSERT(idx > 0); - --idx; - if (m_indices.size() <= idx) { - m_indices.resize(idx + 1, false); - } - m_indices[idx] = true; - } - } - } - - void extract(unsigned_vector& indices) - { - for (unsigned i = 0; i < m_indices.size(); ++i) { - if (m_indices[i]) { - indices.push_back(i); - } - } - } -}; - - - -void sym_mux::collect_indices(expr* e, unsigned_vector& indices) const -{ - indices.reset(); - index_collector collector(*this); - for_each_expr(collector, m_visited, e); - m_visited.reset(); - collector.extract(indices); -} - -class sym_mux::variable_collector { - sym_mux const& m_parent; - vector >& m_vars; -public: - variable_collector(sym_mux const& s, vector >& vars): - m_parent(s), m_vars(vars) {} - - void operator()(expr * e) - { - if (is_app(e)) { - func_decl * sym = to_app(e)->get_decl(); - unsigned idx; - if (m_parent.try_get_index(sym, idx)) { - SASSERT(idx > 0); - --idx; - if (m_vars.size() <= idx) { - m_vars.resize(idx + 1, ptr_vector()); - } - m_vars[idx].push_back(to_app(e)); - } - } - } -}; - -void sym_mux::collect_variables(expr* e, vector >& vars) const -{ - vars.reset(); - variable_collector collector(*this, vars); - for_each_expr(collector, m_visited, e); - m_visited.reset(); -} - -class sym_mux::hmg_checker { - const sym_mux & m_parent; - - bool m_found_idx; - unsigned m_idx; - bool m_multiple_indexes; - -public: - hmg_checker(const sym_mux & parent) : - m_parent(parent), m_found_idx(false), m_multiple_indexes(false) - { - } - - void operator()(expr * e) - { - if (m_multiple_indexes || !is_app(e)) { return; } - - func_decl * sym = to_app(e)->get_decl(); - unsigned sym_idx; - if (!m_parent.try_get_index(sym, sym_idx)) { return; } - - if (!m_found_idx) { - m_found_idx = true; - m_idx = sym_idx; - return; - } - if (m_idx == sym_idx) { return; } - m_multiple_indexes = true; - } - - bool has_multiple_indexes() const - { - return m_multiple_indexes; - } -}; - -bool sym_mux::is_homogenous_formula(expr * e) const -{ - hmg_checker chck(*this); - for_each_expr(chck, m_visited, e); - m_visited.reset(); - return !chck.has_multiple_indexes(); -} - - -struct sym_mux::conv_rewriter_cfg : public default_rewriter_cfg { +namespace { +struct conv_rewriter_cfg : public default_rewriter_cfg { private: ast_manager & m; const sym_mux & m_parent; unsigned m_from_idx; unsigned m_to_idx; bool m_homogenous; + expr_ref_vector m_pinned; public: - conv_rewriter_cfg(const sym_mux & parent, unsigned from_idx, unsigned to_idx, bool homogenous) + conv_rewriter_cfg(const sym_mux & parent, unsigned from_idx, + unsigned to_idx, bool homogenous) : m(parent.get_manager()), m_parent(parent), m_from_idx(from_idx), m_to_idx(to_idx), - m_homogenous(homogenous) {} + m_homogenous(homogenous), m_pinned(m) {(void) m_homogenous;} bool get_subst(expr * s, expr * & t, proof * & t_pr) { @@ -368,241 +148,23 @@ public: app * a = to_app(s); func_decl * sym = a->get_decl(); if (!m_parent.has_index(sym, m_from_idx)) { - (void) m_homogenous; SASSERT(!m_homogenous || !m_parent.is_muxed(sym)); return false; } - func_decl * tgt = m_parent.conv(sym, m_from_idx, m_to_idx); - + func_decl * tgt = m_parent.shift_decl(sym, m_from_idx, m_to_idx); t = m.mk_app(tgt, a->get_args()); + m_pinned.push_back(t); return true; } }; - -void sym_mux::conv_formula(expr * f, unsigned src_idx, unsigned tgt_idx, expr_ref & res, bool homogenous) const -{ - if (src_idx == tgt_idx) { - res = f; - return; - } - conv_rewriter_cfg r_cfg(*this, src_idx, tgt_idx, homogenous); - rewriter_tpl rwr(m, false, r_cfg); - rwr(f, res); } -struct sym_mux::shifting_rewriter_cfg : public default_rewriter_cfg { -private: - ast_manager & m; - const sym_mux & m_parent; - int m_shift; -public: - shifting_rewriter_cfg(const sym_mux & parent, int shift) - : m(parent.get_manager()), - m_parent(parent), - m_shift(shift) {} - - bool get_subst(expr * s, expr * & t, proof * & t_pr) - { - if (!is_app(s)) { return false; } - app * a = to_app(s); - func_decl * sym = a->get_decl(); - - unsigned idx; - if (!m_parent.try_get_index(sym, idx)) { - return false; - } - SASSERT(static_cast(idx) + m_shift >= 0); - func_decl * tgt = m_parent.conv(sym, idx, idx + m_shift); - t = m.mk_app(tgt, a->get_args()); - return true; - } -}; - -void sym_mux::shift_formula(expr * f, int dist, expr_ref & res) const -{ - if (dist == 0) { - res = f; - return; - } - shifting_rewriter_cfg r_cfg(*this, dist); - rewriter_tpl rwr(m, false, r_cfg); - rwr(f, res); -} - -void sym_mux::conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, - expr_ref_vector & res) const -{ - res.reset(); - expr * const * begin = vect.c_ptr(); - expr * const * end = begin + vect.size(); - for (expr * const * it = begin; it != end; it++) { - expr_ref converted(m); - conv_formula(*it, src_idx, tgt_idx, converted); - res.push_back(converted); +void sym_mux::shift_expr(expr * f, unsigned src_idx, unsigned tgt_idx, + expr_ref & res, bool homogenous) const { + if (src_idx == tgt_idx) {res = f;} + else { + conv_rewriter_cfg r_cfg(*this, src_idx, tgt_idx, homogenous); + rewriter_tpl rwr(m, false, r_cfg); + rwr(f, res); } } - -void sym_mux::filter_idx(expr_ref_vector & vect, unsigned idx) const -{ - unsigned i = 0; - while (i < vect.size()) { - expr* e = vect[i].get(); - if (contains(e, idx) && is_homogenous_formula(e, idx)) { - i++; - } else { - //we don't allow mixing states inside vector elements - SASSERT(!contains(e, idx)); - vect[i] = vect.back(); - vect.pop_back(); - } - } -} - -void sym_mux::partition_o_idx( - expr_ref_vector const& lits, - expr_ref_vector& o_lits, - expr_ref_vector& other, unsigned idx) const -{ - - for (unsigned i = 0; i < lits.size(); ++i) { - if (contains(lits[i], idx) && is_homogenous_formula(lits[i], idx)) { - o_lits.push_back(lits[i]); - } else { - other.push_back(lits[i]); - } - } -} - - - -class sym_mux::nonmodel_sym_checker { - const sym_mux & m_parent; - - bool m_found; -public: - nonmodel_sym_checker(const sym_mux & parent) : - m_parent(parent), m_found(false) - { - } - - void operator()(expr * e) - { - if (m_found || !is_app(e)) { return; } - - func_decl * sym = to_app(e)->get_decl(); - - if (m_parent.is_non_model_sym(sym)) { - m_found = true; - } - } - - bool found() const - { - return m_found; - } -}; - -bool sym_mux::has_nonmodel_symbol(expr * e) const -{ - nonmodel_sym_checker chck(*this); - for_each_expr(chck, e); - return chck.found(); -} - -void sym_mux::filter_non_model_lits(expr_ref_vector & vect) const -{ - unsigned i = 0; - while (i < vect.size()) { - if (!has_nonmodel_symbol(vect[i].get())) { - i++; - continue; - } - vect[i] = vect.back(); - vect.pop_back(); - } -} - -class sym_mux::decl_idx_comparator { - const sym_mux & m_parent; -public: - decl_idx_comparator(const sym_mux & parent) - : m_parent(parent) - { } - - bool operator()(func_decl * sym1, func_decl * sym2) - { - unsigned idx1, idx2; - if (!m_parent.try_get_index(sym1, idx1)) { idx1 = UINT_MAX; } - if (!m_parent.try_get_index(sym2, idx2)) { idx2 = UINT_MAX; } - - if (idx1 != idx2) { return idx1 < idx2; } - return lt(sym1->get_name(), sym2->get_name()); - } -}; - -std::string sym_mux::pp_model(const model_core & mdl) const -{ - decl_vector consts; - unsigned sz = mdl.get_num_constants(); - for (unsigned i = 0; i < sz; i++) { - func_decl * d = mdl.get_constant(i); - consts.push_back(d); - } - - std::sort(consts.begin(), consts.end(), decl_idx_comparator(*this)); - - std::stringstream res; - - decl_vector::iterator end = consts.end(); - for (decl_vector::iterator it = consts.begin(); it != end; it++) { - func_decl * d = *it; - std::string name = d->get_name().str(); - const char * arrow = " -> "; - res << name << arrow; - unsigned indent = static_cast(name.length() + strlen(arrow)); - res << mk_pp(mdl.get_const_interp(d), m, indent) << "\n"; - - if (it + 1 != end) { - unsigned idx1, idx2; - if (!try_get_index(*it, idx1)) { idx1 = UINT_MAX; } - if (!try_get_index(*(it + 1), idx2)) { idx2 = UINT_MAX; } - if (idx1 != idx2) { res << "\n"; } - } - } - return res.str(); -} - - -#if 0 - -class sym_mux::index_renamer_cfg : public default_rewriter_cfg { - const sym_mux & m_parent; - unsigned m_idx; - -public: - index_renamer_cfg(const sym_mux & p, unsigned idx) : m_parent(p), m_idx(idx) {} - - bool get_subst(expr * s, expr * & t, proof * & t_pr) - { - if (!is_app(s)) { return false; } - app * a = to_app(s); - if (a->get_family_id() != null_family_id) { - return false; - } - func_decl * sym = a->get_decl(); - unsigned idx; - if (!m_parent.try_get_index(sym, idx)) { - return false; - } - if (m_idx == idx) { - return false; - } - ast_manager& m = m_parent.get_manager(); - symbol name = symbol((sym->get_name().str() + "!").c_str()); - func_decl * tgt = m.mk_func_decl(name, sym->get_arity(), sym->get_domain(), sym->get_range()); - t = m.mk_app(tgt, a->get_num_args(), a->get_args()); - return true; - } -}; - -#endif diff --git a/src/muz/spacer/spacer_sym_mux.h b/src/muz/spacer/spacer_sym_mux.h index a4d10971a..5690370bd 100644 --- a/src/muz/spacer/spacer_sym_mux.h +++ b/src/muz/spacer/spacer_sym_mux.h @@ -1,5 +1,5 @@ /*++ -Copyright (c) 2011 Microsoft Corporation +Copyright (c) 2018 Arie Gurfinkel and Microsoft Corporation Module Name: @@ -7,10 +7,12 @@ Module Name: Abstract: - A symbol multiplexer that helps with having multiple versions of each of a set of symbols. + A symbol multiplexer that helps with having multiple versions of + each of a set of symbols. Author: + Arie Gurfinkel Krystof Hoder (t-khoder) 2011-9-8. Revision History: @@ -20,236 +22,72 @@ Revision History: #ifndef _SYM_MUX_H_ #define _SYM_MUX_H_ +#include + #include "ast/ast.h" #include "util/map.h" #include "util/vector.h" -#include - -class model_core; namespace spacer { class sym_mux { -public: - typedef ptr_vector app_vector; - typedef ptr_vector decl_vector; private: - typedef obj_map sym2u; - typedef obj_map sym2dv; - typedef obj_map sym2sym; - typedef obj_map sym2pred; - typedef hashtable symbols; + class sym_mux_entry { + public: + func_decl_ref m_main; + func_decl_ref_vector m_variants; + sym_mux_entry(ast_manager &m) : m_main(m), m_variants(m) {}; + }; - ast_manager & m; - mutable ast_ref_vector m_ref_holder; - mutable expr_mark m_visited; + typedef obj_map decl2entry_map; + typedef obj_map > mux2entry_map; - mutable unsigned m_next_sym_suffix_idx; - mutable symbols m_used_suffixes; - /** Here we have default suffixes for each of the variants */ - mutable std::vector m_suffixes; + ast_manager &m; + mutable decl2entry_map m_entries; + mutable mux2entry_map m_muxes; + func_decl_ref mk_variant(func_decl *fdecl, unsigned i) const; + void ensure_capacity(sym_mux_entry &entry, unsigned sz) const; - /** - Primary symbol is the 0-th variant. This member maps from primary symbol - to vector of all its variants (including the primary variant). - */ - sym2dv m_prim2all; - - /** - For each symbol contains its variant index - */ - mutable sym2u m_sym2idx; - /** - For each symbol contains its primary variant - */ - mutable sym2sym m_sym2prim; - - /** - Maps prefixes passed to the create_tuple to - the primary symbol created from it. - */ - sym2pred m_prefix2prim; - - /** - Maps pripary symbols to prefixes that were used to create them. - */ - sym2sym m_prim2prefix; - - decl_vector m_prim_preds; - - obj_hashtable m_non_model_syms; - - struct formula_checker; - struct conv_rewriter_cfg; - struct shifting_rewriter_cfg; - class decl_idx_comparator; - class hmg_checker; - class nonmodel_sym_checker; - class index_renamer_cfg; - class index_collector; - class variable_collector; - - std::string get_suffix(unsigned i) const; - void ensure_tuple_size(func_decl * prim, unsigned sz) const; - - expr_ref isolate_o_idx(expr* e, unsigned idx) const; public: - sym_mux(ast_manager & m, const std::vector & suffixes); - + sym_mux(ast_manager & m); + ~sym_mux(); ast_manager & get_manager() const { return m; } - bool is_muxed(func_decl * sym) const { return m_sym2idx.contains(sym); } - - bool try_get_index(func_decl * sym, unsigned & idx) const - { - return m_sym2idx.find(sym, idx); - } - + void register_decl(func_decl *fdecl); + bool find_idx(func_decl * sym, unsigned & idx) const; bool has_index(func_decl * sym, unsigned idx) const - { - unsigned actual_idx; - return try_get_index(sym, actual_idx) && idx == actual_idx; - } + {unsigned v; return find_idx(sym, v) && idx == v;} - /** Return primary symbol. sym must be muxed. */ - func_decl * get_primary(func_decl * sym) const - { - func_decl * prim; - TRUSTME(m_sym2prim.find(sym, prim)); - return prim; - } + bool is_muxed(func_decl *fdecl) const {return m_muxes.contains(fdecl);} /** - Return primary symbol created from prefix, or 0 if the prefix was never used. + \brief Return symbol created from prefix, or 0 if the prefix + was never used. */ - func_decl * try_get_primary_by_prefix(func_decl* prefix) const - { - func_decl * res; - if(!m_prefix2prim.find(prefix, res)) { - return nullptr; - } - return res; - } + func_decl * find_by_decl(func_decl* fdecl, unsigned idx) const; /** - Return symbol created from prefix, or 0 if the prefix was never used. - */ - func_decl * try_get_by_prefix(func_decl* prefix, unsigned idx) const - { - func_decl * prim = try_get_primary_by_prefix(prefix); - if(!prim) { - return nullptr; - } - return conv(prim, 0, idx); - } - - /** - Marks symbol as non-model which means it will not appear in models collected by - get_muxed_cube_from_model function. - This is to take care of auxiliary symbols introduced by the disjunction relations - to relativize lemmas coming from disjuncts. - */ - void mark_as_non_model(func_decl * sym) - { - SASSERT(is_muxed(sym)); - m_non_model_syms.insert(get_primary(sym)); - } - - func_decl * get_or_create_symbol_by_prefix(func_decl* prefix, unsigned idx, - unsigned arity, sort * const * domain, sort * range); - - - - bool is_muxed_lit(expr * e, unsigned idx) const; - - bool is_non_model_sym(func_decl * s) const - { - return is_muxed(s) && m_non_model_syms.contains(get_primary(s)); - } - - /** - Create a multiplexed tuple of propositional constants. - Symbols may be suplied in the tuple vector, - those beyond the size of the array and those with corresponding positions - assigned to zero will be created using prefix. - Tuple length must be at least one. - */ - void create_tuple(func_decl* prefix, unsigned arity, sort * const * domain, sort * range, - unsigned tuple_length, decl_vector & tuple); - - /** - Return true if the only multiplexed symbols which e contains are of index idx. + \brief Return true if the only multiplexed symbols which e contains are + of index idx. */ bool is_homogenous_formula(expr * e, unsigned idx) const; - bool is_homogenous(const expr_ref_vector & vect, unsigned idx) const; - - /** - Return true if all multiplexed symbols which e contains are of one index. - */ - bool is_homogenous_formula(expr * e) const; - - /** - Return true if expression e contains a muxed symbol of index idx. - */ - bool contains(expr * e, unsigned idx) const; - - /** - Collect indices used in expression. - */ - void collect_indices(expr* e, unsigned_vector& indices) const; - - /** - Collect used variables of each index. - */ - void collect_variables(expr* e, vector >& vars) const; - - /** - Convert symbol sym which has to be of src_idx variant into variant tgt_idx. - */ - func_decl * conv(func_decl * sym, unsigned src_idx, unsigned tgt_idx) const; /** - Convert src_idx symbols in formula f variant into tgt_idx. - If homogenous is true, formula cannot contain symbols of other variants. + \brief Convert symbol sym which has to be of src_idx variant + into variant tgt_idx. */ - void conv_formula(expr * f, unsigned src_idx, unsigned tgt_idx, expr_ref & res, bool homogenous = true) const; - void conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, - expr_ref_vector & res) const; + func_decl * shift_decl(func_decl * sym, unsigned src_idx, unsigned tgt_idx) const; /** - Shifts the muxed symbols in f by dist. Dist can be negative, but it should never shift - symbol index to a negative value. + \brief Convert src_idx symbols in formula f variant into + tgt_idx. If homogenous is true, formula cannot contain symbols + of other variants. */ - void shift_formula(expr * f, int dist, expr_ref & res) const; + void shift_expr(expr * f, unsigned src_idx, unsigned tgt_idx, + expr_ref & res, bool homogenous = true) const; - /** - Remove from vect literals (atoms or negations of atoms) of symbols - that contain multiplexed symbols with indexes other than idx. - Each of the literals can contain only symbols multiplexed with one index - (this trivially holds if the literals are propositional). - - Order of elements in vect may be modified by this function - */ - void filter_idx(expr_ref_vector & vect, unsigned idx) const; - - /** - Partition literals into o_literals and others. - */ - void partition_o_idx(expr_ref_vector const& lits, - expr_ref_vector& o_lits, - expr_ref_vector& other, unsigned idx) const; - - bool has_nonmodel_symbol(expr * e) const; - void filter_non_model_lits(expr_ref_vector & vect) const; - - func_decl * const * begin_prim_preds() const { return m_prim_preds.begin(); } - func_decl * const * end_prim_preds() const { return m_prim_preds.end(); } - - void get_muxed_cube_from_model(const model_core & model, expr_ref_vector & res) const; - - std::string pp_model(const model_core & mdl) const; }; } diff --git a/src/muz/spacer/spacer_unsat_core_learner.cpp b/src/muz/spacer/spacer_unsat_core_learner.cpp index f36143c5f..da0d6ec34 100644 --- a/src/muz/spacer/spacer_unsat_core_learner.cpp +++ b/src/muz/spacer/spacer_unsat_core_learner.cpp @@ -17,111 +17,47 @@ Revision History: --*/ #include +#include "ast/for_each_expr.h" +#include "ast/proofs/proof_utils.h" #include "muz/spacer/spacer_unsat_core_learner.h" #include "muz/spacer/spacer_unsat_core_plugin.h" -#include "ast/for_each_expr.h" - -namespace spacer -{ +#include "muz/spacer/spacer_iuc_proof.h" +#include "muz/spacer/spacer_util.h" -unsat_core_learner::~unsat_core_learner() -{ +namespace spacer { + +unsat_core_learner::~unsat_core_learner() { std::for_each(m_plugins.begin(), m_plugins.end(), delete_proc()); - } -void unsat_core_learner::register_plugin(unsat_core_plugin* plugin) -{ +void unsat_core_learner::register_plugin(unsat_core_plugin* plugin) { m_plugins.push_back(plugin); } -void unsat_core_learner::compute_unsat_core(proof *root, expr_set& asserted_b, expr_ref_vector& unsat_core) -{ - // transform proof in order to get a proof which is better suited for unsat-core-extraction - proof_ref pr(root, m); - - reduce_hypotheses(pr); - STRACE("spacer.unsat_core_learner", - verbose_stream() << "Reduced proof:\n" << mk_ismt2_pp(pr, m) << "\n"; - ); - - // compute symbols occurring in B - collect_symbols_b(asserted_b); - +void unsat_core_learner::compute_unsat_core(expr_ref_vector& unsat_core) { // traverse proof - proof_post_order it(root, m); - while (it.hasNext()) - { + proof_post_order it(m_pr.get(), m); + while (it.hasNext()) { proof* currentNode = it.next(); - if (m.get_num_parents(currentNode) == 0) - { - switch(currentNode->get_decl_kind()) - { - - case PR_ASSERTED: // currentNode is an axiom - { - if (asserted_b.contains(m.get_fact(currentNode))) - { - m_b_mark.mark(currentNode, true); - } - else - { - m_a_mark.mark(currentNode, true); - } - break; - } - // currentNode is a hypothesis: - case PR_HYPOTHESIS: - { - m_h_mark.mark(currentNode, true); - break; - } - default: - { - break; - } - } - } - else - { - // collect from parents whether derivation of current node contains A-axioms, B-axioms and hypothesis - bool need_to_mark_a = false; - bool need_to_mark_b = false; - bool need_to_mark_h = false; + if (m.get_num_parents(currentNode) > 0) { bool need_to_mark_closed = true; - for (unsigned i = 0; i < m.get_num_parents(currentNode); ++i) - { - SASSERT(m.is_proof(currentNode->get_arg(i))); - proof* premise = to_app(currentNode->get_arg(i)); - - need_to_mark_a = need_to_mark_a || m_a_mark.is_marked(premise); - need_to_mark_b = need_to_mark_b || m_b_mark.is_marked(premise); - need_to_mark_h = need_to_mark_h || m_h_mark.is_marked(premise); - need_to_mark_closed = need_to_mark_closed && (!m_b_mark.is_marked(premise) || m_closed.is_marked(premise)); + for (proof* premise : m.get_parents(currentNode)) { + need_to_mark_closed &= (!m_pr.is_b_marked(premise) || m_closed.is_marked(premise)); } - // if current node is application of lemma, we know that all hypothesis are removed - if(currentNode->get_decl_kind() == PR_LEMMA) - { - need_to_mark_h = false; - } - - // save results - m_a_mark.mark(currentNode, need_to_mark_a); - m_b_mark.mark(currentNode, need_to_mark_b); - m_h_mark.mark(currentNode, need_to_mark_h); + // save result m_closed.mark(currentNode, need_to_mark_closed); } // we have now collected all necessary information, so we can visit the node // if the node mixes A-reasoning and B-reasoning and contains non-closed premises - if (m_a_mark.is_marked(currentNode) && m_b_mark.is_marked(currentNode) && !m_closed.is_marked(currentNode)) - { + if (m_pr.is_a_marked(currentNode) && + m_pr.is_b_marked(currentNode) && + !m_closed.is_marked(currentNode)) { compute_partial_core(currentNode); // then we need to compute a partial core - // SASSERT(!(m_a_mark.is_marked(currentNode) && m_b_mark.is_marked(currentNode)) || m_closed.is_marked(currentNode)); TODO: doesn't hold anymore if we do the mincut-thing! } } @@ -130,226 +66,39 @@ void unsat_core_learner::compute_unsat_core(proof *root, expr_set& asserted_b, e // TODO: remove duplicates from unsat core? - bool debug_proof = false; - if(debug_proof) - { - // print proof for debugging - verbose_stream() << "\n\nProof:\n"; - std::unordered_map id_to_small_id; - unsigned counter = 0; - - proof_post_order it2(root, m); - while (it2.hasNext()) - { - proof* currentNode = it2.next(); - - SASSERT(id_to_small_id.find(currentNode->get_id()) == id_to_small_id.end()); - id_to_small_id.insert(std::make_pair(currentNode->get_id(), counter)); - - verbose_stream() << counter << " "; - verbose_stream() << "["; - if (is_a_marked(currentNode)) - { - verbose_stream() << "a"; - } - if (is_b_marked(currentNode)) - { - verbose_stream() << "b"; - } - if (is_h_marked(currentNode)) - { - verbose_stream() << "h"; - } - if (is_closed(currentNode)) - { - verbose_stream() << "c"; - } - verbose_stream() << "] "; - - if (m.get_num_parents(currentNode) == 0) - { - switch (currentNode->get_decl_kind()) - { - case PR_ASSERTED: - verbose_stream() << "asserted"; - break; - case PR_HYPOTHESIS: - verbose_stream() << "hypothesis"; - break; - default: - verbose_stream() << "unknown axiom-type"; - break; - } - } - else - { - if (currentNode->get_decl_kind() == PR_LEMMA) - { - verbose_stream() << "lemma"; - } - else if (currentNode->get_decl_kind() == PR_TH_LEMMA) - { - verbose_stream() << "th_lemma"; - func_decl* d = currentNode->get_decl(); - symbol sym; - if (d->get_num_parameters() >= 2 && // the Farkas coefficients are saved in the parameters of step - d->get_parameter(0).is_symbol(sym) && sym == "arith" && // the first two parameters are "arith", "farkas", - d->get_parameter(1).is_symbol(sym) && sym == "farkas") - { - verbose_stream() << "(farkas)"; - } - else - { - verbose_stream() << "(other)"; - } - } - else - { - verbose_stream() << "step"; - } - verbose_stream() << " from "; - for (int i = m.get_num_parents(currentNode) - 1; i >= 0 ; --i) - { - proof* premise = to_app(currentNode->get_arg(i)); - unsigned premise_small_id = id_to_small_id[premise->get_id()]; - if (i > 0) - { - verbose_stream() << premise_small_id << ", "; - } - else - { - verbose_stream() << premise_small_id; - } - - } - } - if (currentNode->get_decl_kind() == PR_TH_LEMMA || (is_a_marked(currentNode) && is_b_marked(currentNode)) || is_h_marked(currentNode) || (!is_a_marked(currentNode) && !is_b_marked(currentNode))) - { - verbose_stream() << std::endl; - } - else - { - verbose_stream() << ": " << mk_pp(m.get_fact(currentNode), m) << std::endl; - } - ++counter; - } - } // move all lemmas into vector - for (expr* const* it = m_unsat_core.begin(); it != m_unsat_core.end(); ++it) - { - unsat_core.push_back(*it); + for (expr* e : m_unsat_core) { + unsat_core.push_back(e); } } -void unsat_core_learner::compute_partial_core(proof* step) -{ - for (unsat_core_plugin** it=m_plugins.begin(), **end = m_plugins.end (); it != end && !m_closed.is_marked(step); ++it) - { - unsat_core_plugin* plugin = *it; +void unsat_core_learner::compute_partial_core(proof* step) { + for (unsat_core_plugin* plugin : m_plugins) { + if (m_closed.is_marked(step)) break; plugin->compute_partial_core(step); } } -void unsat_core_learner::finalize() -{ - for (unsat_core_plugin** it=m_plugins.begin(); it != m_plugins.end(); ++it) - { - unsat_core_plugin* plugin = *it; +void unsat_core_learner::finalize() { + for (unsat_core_plugin* plugin : m_plugins) { plugin->finalize(); } } - -bool unsat_core_learner::is_a_marked(proof* p) -{ - return m_a_mark.is_marked(p); -} -bool unsat_core_learner::is_b_marked(proof* p) -{ - return m_b_mark.is_marked(p); -} -bool unsat_core_learner::is_h_marked(proof* p) -{ - return m_h_mark.is_marked(p); -} -bool unsat_core_learner::is_closed(proof*p) -{ +bool unsat_core_learner::is_closed(proof* p) { return m_closed.is_marked(p); } -void unsat_core_learner::set_closed(proof* p, bool value) -{ + +void unsat_core_learner::set_closed(proof* p, bool value) { m_closed.mark(p, value); } - void unsat_core_learner::add_lemma_to_core(expr* lemma) - { - m_unsat_core.push_back(lemma); - } - - -class collect_pure_proc { - func_decl_set& m_symbs; -public: - collect_pure_proc(func_decl_set& s):m_symbs(s) {} - - void operator()(app* a) { - if (a->get_family_id() == null_family_id) { - m_symbs.insert(a->get_decl()); - } - } - void operator()(var*) {} - void operator()(quantifier*) {} -}; - -void unsat_core_learner::collect_symbols_b(const expr_set& axioms_b) -{ - expr_mark visited; - collect_pure_proc proc(m_symbols_b); - for (expr_set::iterator it = axioms_b.begin(); it != axioms_b.end(); ++it) - { - for_each_expr(proc, visited, *it); - } +bool unsat_core_learner::is_b_open(proof *p) { + return m_pr.is_b_marked(p) && !is_closed (p); } -class is_pure_expr_proc { - func_decl_set const& m_symbs; - array_util m_au; -public: - struct non_pure {}; - - is_pure_expr_proc(func_decl_set const& s, ast_manager& m): - m_symbs(s), - m_au (m) - {} - - void operator()(app* a) { - if (a->get_family_id() == null_family_id) { - if (!m_symbs.contains(a->get_decl())) { - throw non_pure(); - } - } - else if (a->get_family_id () == m_au.get_family_id () && - a->is_app_of (a->get_family_id (), OP_ARRAY_EXT)) { - throw non_pure(); - } - } - void operator()(var*) {} - void operator()(quantifier*) {} -}; - -bool unsat_core_learner::only_contains_symbols_b(expr* expr) const -{ - is_pure_expr_proc proc(m_symbols_b, m); - try { - for_each_expr(proc, expr); - } - catch (is_pure_expr_proc::non_pure) - { - return false; - } - return true; +void unsat_core_learner::add_lemma_to_core(expr* lemma) { + m_unsat_core.push_back(lemma); } - - } diff --git a/src/muz/spacer/spacer_unsat_core_learner.h b/src/muz/spacer/spacer_unsat_core_learner.h index 87238b5fd..327b12eb6 100644 --- a/src/muz/spacer/spacer_unsat_core_learner.h +++ b/src/muz/spacer/spacer_unsat_core_learner.h @@ -6,7 +6,8 @@ Module Name: spacer_unsat_core_learner.h Abstract: - itp cores + + itp cores Author: Bernhard Gleiss @@ -20,21 +21,23 @@ Revision History: #include "ast/ast.h" #include "muz/spacer/spacer_util.h" -#include "ast/proofs/proof_utils.h" +#include "muz/spacer/spacer_proof_utils.h" namespace spacer { class unsat_core_plugin; - class unsat_core_learner - { + class iuc_proof; + class unsat_core_learner { typedef obj_hashtable expr_set; public: - unsat_core_learner(ast_manager& m) : m(m), m_unsat_core(m) {}; + unsat_core_learner(ast_manager& m, iuc_proof& pr) : + m(m), m_pr(pr), m_unsat_core(m) {}; virtual ~unsat_core_learner(); ast_manager& m; + iuc_proof& m_pr; /* * register a plugin for computation of partial unsat cores @@ -45,51 +48,33 @@ namespace spacer { /* * compute unsat core using the registered unsat-core-plugins */ - void compute_unsat_core(proof* root, expr_set& asserted_b, expr_ref_vector& unsat_core); + void compute_unsat_core(expr_ref_vector& unsat_core); /* * getter/setter methods for data structures exposed to plugins - * the following invariants can be assumed and need to be maintained by the plugins: - * - a node is a-marked iff it is derived using at least one asserted proof step from A. - * - a node is b-marked iff its derivation contains no asserted proof steps from A and - * no hypothesis (with the additional complication that lemmas conceptually remove hypothesis) - * - a node is h-marked, iff it is derived using at least one hypothesis + * the following invariant can be assumed and need to be maintained by the plugins: * - a node is closed, iff it has already been interpolated, i.e. its contribution is * already covered by the unsat-core. */ - bool is_a_marked(proof* p); - bool is_b_marked(proof* p); - bool is_h_marked(proof* p); bool is_closed(proof* p); void set_closed(proof* p, bool value); + bool is_b_open (proof *p); + /* * adds a lemma to the unsat core */ void add_lemma_to_core(expr* lemma); - /* - * helper method, which can be used by plugins - * returns true iff all symbols of expr occur in some b-asserted formula. - * must only be called after a call to collect_symbols_b. - */ - bool only_contains_symbols_b(expr* expr) const; - bool is_b_pure (proof *p) - {return !is_h_marked (p) && only_contains_symbols_b (m.get_fact (p));} - bool is_b_open (proof *p) - { return is_b_marked (p) && !is_closed (p); } - private: ptr_vector m_plugins; - func_decl_set m_symbols_b; // symbols, which occur in any b-asserted formula - void collect_symbols_b(const expr_set& axioms_b); - - ast_mark m_a_mark; - ast_mark m_b_mark; - ast_mark m_h_mark; ast_mark m_closed; - expr_ref_vector m_unsat_core; // collects the lemmas of the unsat-core, will at the end be inserted into unsat_core. + /* + * collects the lemmas of the unsat-core + * will at the end be inserted into unsat_core. + */ + expr_ref_vector m_unsat_core; /* * computes partial core for step by delegating computation to plugins @@ -101,7 +86,6 @@ namespace spacer { */ void finalize(); }; - } #endif diff --git a/src/muz/spacer/spacer_unsat_core_plugin.cpp b/src/muz/spacer/spacer_unsat_core_plugin.cpp index a1a937de0..8fb5feeef 100644 --- a/src/muz/spacer/spacer_unsat_core_plugin.cpp +++ b/src/muz/spacer/spacer_unsat_core_plugin.cpp @@ -6,7 +6,7 @@ Module Name: spacer_unsat_core_plugin.cpp Abstract: - plugin for itp cores + plugin for itp cores Author: Bernhard Gleiss @@ -30,311 +30,248 @@ Revision History: #include "muz/spacer/spacer_matrix.h" #include "muz/spacer/spacer_unsat_core_plugin.h" #include "muz/spacer/spacer_unsat_core_learner.h" +#include "muz/spacer/spacer_iuc_proof.h" -namespace spacer -{ +namespace spacer { - -void unsat_core_plugin_lemma::compute_partial_core(proof* step) -{ - SASSERT(m_learner.is_a_marked(step)); - SASSERT(m_learner.is_b_marked(step)); - - for (unsigned i = 0; i < m_learner.m.get_num_parents(step); ++i) + unsat_core_plugin::unsat_core_plugin(unsat_core_learner& learner): + m(learner.m), m_learner(learner) {}; + + void unsat_core_plugin_lemma::compute_partial_core(proof* step) { + SASSERT(m_learner.m_pr.is_a_marked(step)); + SASSERT(m_learner.m_pr.is_b_marked(step)); + + for (proof* premise : m.get_parents(step)) { + + if (m_learner.is_b_open (premise)) { + // by IH, premises that are AB marked are already closed + SASSERT(!m_learner.m_pr.is_a_marked(premise)); + add_lowest_split_to_core(premise); + } + } + m_learner.set_closed(step, true); + } + + void unsat_core_plugin_lemma::add_lowest_split_to_core(proof* step) const { - SASSERT(m_learner.m.is_proof(step->get_arg(i))); - proof* premise = to_app(step->get_arg(i)); - - if (m_learner.is_b_open (premise)) - { - // by IH, premises that are AB marked are already closed - SASSERT(!m_learner.is_a_marked(premise)); - add_lowest_split_to_core(premise); + SASSERT(m_learner.is_b_open(step)); + + ptr_buffer todo; + todo.push_back(step); + + while (!todo.empty()) { + proof* pf = todo.back(); + todo.pop_back(); + + // if current step hasn't been processed, + if (!m_learner.is_closed(pf)) { + m_learner.set_closed(pf, true); + // the step is b-marked and not closed. + // by I.H. the step must be already visited + // so if it is also a-marked, it must be closed + SASSERT(m_learner.m_pr.is_b_marked(pf)); + SASSERT(!m_learner.m_pr.is_a_marked(pf)); + + // the current step needs to be interpolated: + expr* fact = m.get_fact(pf); + // if we trust the current step and we are able to use it + if (m_learner.m_pr.is_b_pure (pf) && + (m.is_asserted(pf) || is_literal(m, fact))) { + // just add it to the core + m_learner.add_lemma_to_core(fact); + } + // otherwise recurse on premises + else { + for (proof* premise : m.get_parents(pf)) + if (m_learner.is_b_open(premise)) + todo.push_back(premise); + } + + } } } - m_learner.set_closed(step, true); -} -void unsat_core_plugin_lemma::add_lowest_split_to_core(proof* step) const -{ - SASSERT(m_learner.is_b_open(step)); - ast_manager &m = m_learner.m; - ptr_vector todo; - todo.push_back(step); - - while (!todo.empty()) + void unsat_core_plugin_farkas_lemma::compute_partial_core(proof* step) { - proof* pf = todo.back(); - todo.pop_back(); - - // if current step hasn't been processed, - if (!m_learner.is_closed(pf)) - { - m_learner.set_closed(pf, true); - // the step is b-marked and not closed. - // by I.H. the step must be already visited - // so if it is also a-marked, it must be closed - SASSERT(m_learner.is_b_marked(pf)); - SASSERT(!m_learner.is_a_marked(pf)); - - // the current step needs to be interpolated: - expr* fact = m_learner.m.get_fact(pf); - // if we trust the current step and we are able to use it - if (m_learner.is_b_pure (pf) && - (m.is_asserted(pf) || is_literal(m, fact))) - { - // just add it to the core - m_learner.add_lemma_to_core(fact); + SASSERT(m_learner.m_pr.is_a_marked(step)); + SASSERT(m_learner.m_pr.is_b_marked(step)); + // XXX this assertion should be true so there is no need to check for it + SASSERT (!m_learner.is_closed (step)); + func_decl* d = step->get_decl(); + symbol sym; + if (!m_learner.is_closed(step) && // if step is not already interpolated + is_farkas_lemma(m, step)) { + // weaker check: d->get_num_parameters() >= m.get_num_parents(step) + 2 + + SASSERT(d->get_num_parameters() == m.get_num_parents(step) + 2); + SASSERT(m.has_fact(step)); + + coeff_lits_t coeff_lits; + expr_ref_vector pinned(m); + + /* The farkas lemma represents a subproof starting from premise(-set)s A, BNP and BP(ure) and + * ending in a disjunction D. We need to compute the contribution of BP, i.e. a formula, which + * is entailed by BP and together with A and BNP entails D. + * + * Let Fark(F) be the farkas coefficient for F. We can use the fact that + * (A*Fark(A) + BNP*Fark(BNP) + BP*Fark(BP) + (neg D)*Fark(D)) => false. (E1) + * We further have that A+B => C implies (A \land B) => C. (E2) + * + * Alternative 1: + * From (E1) immediately get that BP*Fark(BP) is a solution. + * + * Alternative 2: + * We can rewrite (E2) to rewrite (E1) to + * (BP*Fark(BP)) => (neg(A*Fark(A) + BNP*Fark(BNP) + (neg D)*Fark(D))) (E3) + * and since we can derive (A*Fark(A) + BNP*Fark(BNP) + (neg D)*Fark(D)) from + * A, BNP and D, we also know that it is inconsisent. Therefore + * neg(A*Fark(A) + BNP*Fark(BNP) + (neg D)*Fark(D)) is a solution. + * + * Finally we also need the following workaround: + * 1) Although we know from theory, that the Farkas coefficients are always nonnegative, + * the Farkas coefficients provided by arith_core are sometimes negative (must be a bug) + * as workaround we take the absolute value of the provided coefficients. + */ + parameter const* params = d->get_parameters() + 2; // point to the first Farkas coefficient + + STRACE("spacer.farkas", + verbose_stream() << "Farkas input: "<< "\n"; + for (unsigned i = 0; i < m.get_num_parents(step); ++i) { + proof * prem = m.get_parent(step, i); + rational coef = params[i].get_rational(); + bool b_pure = m_learner.m_pr.is_b_pure (prem); + verbose_stream() << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m.get_fact(prem), m) << "\n"; + } + ); + + bool can_be_closed = true; + + for (unsigned i = 0; i < m.get_num_parents(step); ++i) { + proof * premise = m.get_parent(step, i); + + if (m_learner.is_b_open (premise)) { + SASSERT(!m_learner.m_pr.is_a_marked(premise)); + + if (m_learner.m_pr.is_b_pure (step)) { + if (!m_use_constant_from_a) { + rational coefficient = params[i].get_rational(); + coeff_lits.push_back(std::make_pair(abs(coefficient), (app*)m.get_fact(premise))); + } + } + else { + can_be_closed = false; + + if (m_use_constant_from_a) { + rational coefficient = params[i].get_rational(); + coeff_lits.push_back(std::make_pair(abs(coefficient), (app*)m.get_fact(premise))); + } + } + } + else { + if (m_use_constant_from_a) { + rational coefficient = params[i].get_rational(); + coeff_lits.push_back(std::make_pair(abs(coefficient), (app*)m.get_fact(premise))); + } + } } - // otherwise recurse on premises - else - { - for (unsigned i = 0, sz = m_learner.m.get_num_parents(pf); - i < sz; ++i) - { - SASSERT(m_learner.m.is_proof(pf->get_arg(i))); - proof* premise = m.get_parent (pf, i); - if (m_learner.is_b_open(premise)) { - todo.push_back(premise); + + if (m_use_constant_from_a) { + params += m.get_num_parents(step); // point to the first Farkas coefficient, which corresponds to a formula in the conclusion + + // the conclusion can either be a single formula or a disjunction of several formulas, we have to deal with both situations + if (m.get_num_parents(step) + 2 < d->get_num_parameters()) { + unsigned num_args = 1; + expr* conclusion = m.get_fact(step); + expr* const* args = &conclusion; + if (m.is_or(conclusion)) { + app* _or = to_app(conclusion); + num_args = _or->get_num_args(); + args = _or->get_args(); + } + SASSERT(m.get_num_parents(step) + 2 + num_args == d->get_num_parameters()); + + bool_rewriter brw(m); + for (unsigned i = 0; i < num_args; ++i) { + expr* premise = args[i]; + + expr_ref negatedPremise(m); + brw.mk_not(premise, negatedPremise); + pinned.push_back(negatedPremise); + rational coefficient = params[i].get_rational(); + coeff_lits.push_back(std::make_pair(abs(coefficient), to_app(negatedPremise))); } } } - } - } -} - - -void unsat_core_plugin_farkas_lemma::compute_partial_core(proof* step) -{ - ast_manager &m = m_learner.m; - SASSERT(m_learner.is_a_marked(step)); - SASSERT(m_learner.is_b_marked(step)); - // XXX this assertion should be true so there is no need to check for it - SASSERT (!m_learner.is_closed (step)); - func_decl* d = step->get_decl(); - symbol sym; - if(!m_learner.is_closed(step) && // if step is not already interpolated - step->get_decl_kind() == PR_TH_LEMMA && // and step is a Farkas lemma - d->get_num_parameters() >= 2 && // the Farkas coefficients are saved in the parameters of step - d->get_parameter(0).is_symbol(sym) && sym == "arith" && // the first two parameters are "arith", "farkas", - d->get_parameter(1).is_symbol(sym) && sym == "farkas" && - d->get_num_parameters() >= m_learner.m.get_num_parents(step) + 2) // the following parameters are the Farkas coefficients - { - SASSERT(m_learner.m.has_fact(step)); - - ptr_vector literals; - vector coefficients; - - /* The farkas lemma represents a subproof starting from premise(-set)s A, BNP and BP(ure) and - * ending in a disjunction D. We need to compute the contribution of BP, i.e. a formula, which - * is entailed by BP and together with A and BNP entails D. - * - * Let Fark(F) be the farkas coefficient for F. We can use the fact that - * (A*Fark(A) + BNP*Fark(BNP) + BP*Fark(BP) + (neg D)*Fark(D)) => false. (E1) - * We further have that A+B => C implies (A \land B) => C. (E2) - * - * Alternative 1: - * From (E1) immediately get that BP*Fark(BP) is a solution. - * - * Alternative 2: - * We can rewrite (E2) to rewrite (E1) to - * (BP*Fark(BP)) => (neg(A*Fark(A) + BNP*Fark(BNP) + (neg D)*Fark(D))) (E3) - * and since we can derive (A*Fark(A) + BNP*Fark(BNP) + (neg D)*Fark(D)) from - * A, BNP and D, we also know that it is inconsisent. Therefore - * neg(A*Fark(A) + BNP*Fark(BNP) + (neg D)*Fark(D)) is a solution. - * - * Finally we also need the following workaround: - * 1) Although we know from theory, that the Farkas coefficients are always nonnegative, - * the Farkas coefficients provided by arith_core are sometimes negative (must be a bug) - * as workaround we take the absolute value of the provided coefficients. - */ - parameter const* params = d->get_parameters() + 2; // point to the first Farkas coefficient - - STRACE("spacer.farkas", - verbose_stream() << "Farkas input: "<< "\n"; - for (unsigned i = 0; i < m_learner.m.get_num_parents(step); ++i) - { - SASSERT(m_learner.m.is_proof(step->get_arg(i))); - proof *prem = m.get_parent (step, i); - - rational coef; - VERIFY(params[i].is_rational(coef)); - - bool b_pure = m_learner.is_b_pure (prem); - verbose_stream() << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m_learner.m.get_fact(prem), m_learner.m) << "\n"; - } - ); - - bool can_be_closed = true; - - for(unsigned i = 0; i < m.get_num_parents(step); ++i) - { - SASSERT(m_learner.m.is_proof(step->get_arg(i))); - proof * premise = m.get_parent (step, i); - - if (m_learner.is_b_open (premise)) - { - SASSERT(!m_learner.is_a_marked(premise)); - - if (m_learner.is_b_pure (step)) - { - if (!m_use_constant_from_a) - { - rational coefficient; - VERIFY(params[i].is_rational(coefficient)); - literals.push_back(to_app(m_learner.m.get_fact(premise))); - coefficients.push_back(abs(coefficient)); - } - } - else - { - can_be_closed = false; - - if (m_use_constant_from_a) - { - rational coefficient; - VERIFY(params[i].is_rational(coefficient)); - literals.push_back(to_app(m_learner.m.get_fact(premise))); - coefficients.push_back(abs(coefficient)); - } - } - } - else - { - if (m_use_constant_from_a) - { - rational coefficient; - VERIFY(params[i].is_rational(coefficient)); - literals.push_back(to_app(m_learner.m.get_fact(premise))); - coefficients.push_back(abs(coefficient)); - } + // only if all b-premises can be used directly, add the farkas core and close the step + if (can_be_closed) { + m_learner.set_closed(step, true); + + expr_ref res = compute_linear_combination(coeff_lits); + + m_learner.add_lemma_to_core(res); } } + } - if (m_use_constant_from_a) - { - params += m_learner.m.get_num_parents(step); // point to the first Farkas coefficient, which corresponds to a formula in the conclusion + expr_ref unsat_core_plugin_farkas_lemma::compute_linear_combination(const coeff_lits_t& coeff_lits) + { - // the conclusion can either be a single formula or a disjunction of several formulas, we have to deal with both situations - if (m_learner.m.get_num_parents(step) + 2 < d->get_num_parameters()) - { - unsigned num_args = 1; - expr* conclusion = m_learner.m.get_fact(step); - expr* const* args = &conclusion; - if (m_learner.m.is_or(conclusion)) - { - app* _or = to_app(conclusion); - num_args = _or->get_num_args(); - args = _or->get_args(); - } - SASSERT(m_learner.m.get_num_parents(step) + 2 + num_args == d->get_num_parameters()); - - bool_rewriter brw(m_learner.m); - for (unsigned i = 0; i < num_args; ++i) - { - expr* premise = args[i]; - - expr_ref negatedPremise(m_learner.m); - brw.mk_not(premise, negatedPremise); - literals.push_back(to_app(negatedPremise)); - - rational coefficient; - VERIFY(params[i].is_rational(coefficient)); - coefficients.push_back(abs(coefficient)); - } - } + smt::farkas_util util(m); + if (m_use_constant_from_a) { + util.set_split_literals (m_split_literals); // small optimization: if flag m_split_literals is set, then preserve diff constraints } - - // only if all b-premises can be used directly, add the farkas core and close the step - if (can_be_closed) - { - m_learner.set_closed(step, true); - - expr_ref res(m_learner.m); - compute_linear_combination(coefficients, literals, res); - - m_learner.add_lemma_to_core(res); + for (auto& p : coeff_lits) { + util.add(p.first, p.second); + } + if (m_use_constant_from_a) { + return util.get(); + } + else { + return expr_ref(mk_not(m, util.get()), m); } } -} - -void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector& coefficients, const ptr_vector& literals, expr_ref& res) -{ - SASSERT(literals.size() == coefficients.size()); - - ast_manager& m = res.get_manager(); - smt::farkas_util util(m); - if (m_use_constant_from_a) - { - util.set_split_literals (m_split_literals); // small optimization: if flag m_split_literals is set, then preserve diff constraints - } - for(unsigned i = 0; i < literals.size(); ++i) - { - util.add(coefficients[i], literals[i]); - } - if (m_use_constant_from_a) - { - res = util.get(); - } - else - { - expr_ref negated_linear_combination = util.get(); - res = mk_not(m, negated_linear_combination); - } -} void unsat_core_plugin_farkas_lemma_optimized::compute_partial_core(proof* step) { - SASSERT(m_learner.is_a_marked(step)); - SASSERT(m_learner.is_b_marked(step)); + SASSERT(m_learner.m_pr.is_a_marked(step)); + SASSERT(m_learner.m_pr.is_b_marked(step)); func_decl* d = step->get_decl(); symbol sym; - if(!m_learner.is_closed(step) && // if step is not already interpolated - step->get_decl_kind() == PR_TH_LEMMA && // and step is a Farkas lemma - d->get_num_parameters() >= 2 && // the Farkas coefficients are saved in the parameters of step - d->get_parameter(0).is_symbol(sym) && sym == "arith" && // the first two parameters are "arith", "farkas", - d->get_parameter(1).is_symbol(sym) && sym == "farkas" && - d->get_num_parameters() >= m_learner.m.get_num_parents(step) + 2) // the following parameters are the Farkas coefficients - { - SASSERT(m_learner.m.has_fact(step)); + if (!m_learner.is_closed(step) && // if step is not already interpolated + is_farkas_lemma(m, step)) { + SASSERT(d->get_num_parameters() == m.get_num_parents(step) + 2); + SASSERT(m.has_fact(step)); - vector > linear_combination; // collects all summands of the linear combination + coeff_lits_t linear_combination; // collects all summands of the linear combination parameter const* params = d->get_parameters() + 2; // point to the first Farkas coefficient STRACE("spacer.farkas", - verbose_stream() << "Farkas input: "<< "\n"; - for (unsigned i = 0; i < m_learner.m.get_num_parents(step); ++i) - { - SASSERT(m_learner.m.is_proof(step->get_arg(i))); - proof *prem = m.get_parent (step, i); - - rational coef; - VERIFY(params[i].is_rational(coef)); - - bool b_pure = m_learner.is_b_pure (prem); - verbose_stream() << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m_learner.m.get_fact(prem), m_learner.m) << "\n"; - } - ); + verbose_stream() << "Farkas input: "<< "\n"; + for (unsigned i = 0; i < m.get_num_parents(step); ++i) { + proof * prem = m.get_parent(step, i); + rational coef = params[i].get_rational(); + bool b_pure = m_learner.m_pr.is_b_pure (prem); + verbose_stream() << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m.get_fact(prem), m_learner.m) << "\n"; + } + ); bool can_be_closed = true; - for(unsigned i = 0; i < m_learner.m.get_num_parents(step); ++i) - { - SASSERT(m_learner.m.is_proof(step->get_arg(i))); - proof * premise = m.get_parent (step, i); + for (unsigned i = 0; i < m.get_num_parents(step); ++i) { + proof * premise = m.get_parent(step, i); - if (m_learner.is_b_marked(premise) && !m_learner.is_closed(premise)) + if (m_learner.m_pr.is_b_marked(premise) && !m_learner.is_closed(premise)) { - SASSERT(!m_learner.is_a_marked(premise)); + SASSERT(!m_learner.m_pr.is_a_marked(premise)); - if (m_learner.only_contains_symbols_b(m_learner.m.get_fact(premise)) && !m_learner.is_h_marked(premise)) + if (m_learner.m_pr.is_b_pure(premise)) { - rational coefficient; - VERIFY(params[i].is_rational(coefficient)); - linear_combination.push_back(std::make_pair(to_app(m_learner.m.get_fact(premise)), abs(coefficient))); + rational coefficient = params[i].get_rational(); + linear_combination.push_back + (std::make_pair(abs(coefficient), to_app(m.get_fact(premise)))); } else { @@ -357,99 +294,21 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector& pair1, const std::pair& pair2) const + inline bool operator() (const std::pair& pair1, const std::pair& pair2) const { - return (pair1.first->get_id() < pair2.first->get_id()); + return (pair1.second->get_id() < pair2.second->get_id()); } }; void unsat_core_plugin_farkas_lemma_optimized::finalize() { - if(m_linear_combinations.empty()) + if (m_linear_combinations.empty()) { return; } DEBUG_CODE( for (auto& linear_combination : m_linear_combinations) { - SASSERT(linear_combination.size() > 0); - }); - - // 1. construct ordered basis - ptr_vector ordered_basis; - obj_map map; - unsigned counter = 0; - for (const auto& linear_combination : m_linear_combinations) - { - for (const auto& pair : linear_combination) - { - if (!map.contains(pair.first)) - { - ordered_basis.push_back(pair.first); - map.insert(pair.first, counter++); - } - } - } - - // 2. populate matrix - spacer_matrix matrix(m_linear_combinations.size(), ordered_basis.size()); - - for (unsigned i=0; i < m_linear_combinations.size(); ++i) - { - auto linear_combination = m_linear_combinations[i]; - for (const auto& pair : linear_combination) - { - matrix.set(i, map[pair.first], pair.second); - } - } - - // 3. perform gaussian elimination - unsigned i = matrix.perform_gaussian_elimination(); - - // 4. extract linear combinations from matrix and add result to core - for (unsigned k=0; k < i; k++)// i points to the row after the last row which is non-zero - { - ptr_vector literals; - vector coefficients; - for (unsigned l=0; l < matrix.num_cols(); ++l) - { - if (!matrix.get(k,l).is_zero()) - { - literals.push_back(ordered_basis[l]); - coefficients.push_back(matrix.get(k,l)); - } - } - SASSERT(literals.size() > 0); - expr_ref linear_combination(m); - compute_linear_combination(coefficients, literals, linear_combination); - - m_learner.add_lemma_to_core(linear_combination); - } - - } - - void unsat_core_plugin_farkas_lemma_optimized::compute_linear_combination(const vector& coefficients, const ptr_vector& literals, expr_ref& res) - { - SASSERT(literals.size() == coefficients.size()); - - ast_manager& m = res.get_manager(); - smt::farkas_util util(m); - for(unsigned i = 0; i < literals.size(); ++i) - { - util.add(coefficients[i], literals[i]); - } - expr_ref negated_linear_combination = util.get(); - SASSERT(m.is_not(negated_linear_combination)); - res = mk_not(m, negated_linear_combination); //TODO: rewrite the get-method to return nonnegated stuff? - } - - - void unsat_core_plugin_farkas_lemma_bounded::finalize() { - if (m_linear_combinations.empty()) { - return; - } - DEBUG_CODE( - for (auto& linear_combination : m_linear_combinations) { - SASSERT(linear_combination.size() > 0); + SASSERT(!linear_combination.empty()); }); // 1. construct ordered basis @@ -458,9 +317,72 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector ordered_basis; + obj_map map; + unsigned counter = 0; + for (const auto& linear_combination : m_linear_combinations) { + for (const auto& pair : linear_combination) { + if (!map.contains(pair.second)) { + ordered_basis.push_back(pair.second); + map.insert(pair.second, counter++); } } } @@ -471,7 +393,7 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector coeffs; - for (unsigned i=0; i < matrix.num_rows(); ++i) { + for (unsigned i = 0; i < matrix.num_rows(); ++i) { coeffs.push_back(expr_ref_vector(m)); } vector bounded_vectors; - for (unsigned j=0; j < matrix.num_cols(); ++j) - { + for (unsigned j = 0; j < matrix.num_cols(); ++j) { bounded_vectors.push_back(expr_ref_vector(m)); } // 4. find smallest n using guess and check algorithm - for(unsigned n = 1; true; ++n) + for (unsigned n = 1; true; ++n) { params_ref p; p.set_bool("model", true); - scoped_ptr s = mk_smt_solver(m, p, symbol::null); // TODO: incremental version? + solver_ref s = mk_smt_solver(m, p, symbol::null); // TODO: incremental version? // add new variables w_in, - for (unsigned i=0; i < matrix.num_rows(); ++i) - { + for (unsigned i = 0; i < matrix.num_rows(); ++i) { std::string name = "w_" + std::to_string(i) + std::to_string(n); - - func_decl_ref decl(m); - decl = m.mk_func_decl(symbol(name.c_str()), 0, (sort*const*)nullptr, util.mk_int()); - coeffs[i].push_back(m.mk_const(decl)); + coeffs[i].push_back(m.mk_const(name, util.mk_int())); } // we need s_jn - for (unsigned j=0; j < matrix.num_cols(); ++j) - { + for (unsigned j = 0; j < matrix.num_cols(); ++j) { std::string name = "s_" + std::to_string(j) + std::to_string(n); - - func_decl_ref decl(m); - decl = m.mk_func_decl(symbol(name.c_str()), 0, (sort*const*)nullptr, util.mk_int()); - - expr_ref s_jn(m); - s_jn = m.mk_const(decl); - - bounded_vectors[j].push_back(s_jn); + bounded_vectors[j].push_back(m.mk_const(name, util.mk_int())); } // assert bounds for all s_jn - for (unsigned l=0; l < n; ++l) { - for (unsigned j=0; j < matrix.num_cols(); ++j) { + for (unsigned l = 0; l < n; ++l) { + for (unsigned j = 0; j < matrix.num_cols(); ++j) { expr* s_jn = bounded_vectors[j][l].get(); - expr_ref lb(util.mk_le(util.mk_int(0), s_jn), m); expr_ref ub(util.mk_le(s_jn, util.mk_int(1)), m); s->assert_expr(lb); s->assert_expr(ub); } } - + // assert: forall i,j: a_ij = sum_k w_ik * s_jk - for (unsigned i=0; i < matrix.num_rows(); ++i) - { - for (unsigned j=0; j < matrix.num_cols(); ++j) - { + for (unsigned i = 0; i < matrix.num_rows(); ++i) { + for (unsigned j = 0; j < matrix.num_cols(); ++j) { SASSERT(matrix.get(i, j).is_int()); - app_ref a_ij(util.mk_numeral(matrix.get(i,j), true),m); - - app_ref sum(m); - sum = util.mk_int(0); - for (unsigned k=0; k < n; ++k) - { + app_ref a_ij(util.mk_numeral(matrix.get(i,j), true), m); + + app_ref sum(util.mk_int(0), m); + for (unsigned k = 0; k < n; ++k) { sum = util.mk_add(sum, util.mk_mul(coeffs[i][k].get(), bounded_vectors[j][k].get())); - } - expr_ref eq(m.mk_eq(a_ij, sum),m); - s->assert_expr(eq); } + expr_ref eq(m.mk_eq(a_ij, sum), m); + s->assert_expr(eq); } - + } + // check result - lbool res = s->check_sat(0,nullptr); + lbool res = s->check_sat(0, nullptr); // if sat extract model and add corresponding linear combinations to core if (res == lbool::l_true) { model_ref model; s->get_model(model); - - for (unsigned k=0; k < n; ++k) { - ptr_vector literals; - vector coefficients; - for (unsigned j=0; j < matrix.num_cols(); ++j) { + + for (unsigned k = 0; k < n; ++k) { + coeff_lits_t coeff_lits; + for (unsigned j = 0; j < matrix.num_cols(); ++j) { expr_ref evaluation(m); - - model.get()->eval(bounded_vectors[j][k].get(), evaluation, false); + + evaluation = (*model)(bounded_vectors[j][k].get()); if (!util.is_zero(evaluation)) { - literals.push_back(ordered_basis[j]); - coefficients.push_back(rational(1)); + coeff_lits.push_back(std::make_pair(rational(1), ordered_basis[j])); } } - SASSERT(!literals.empty()); // since then previous outer loop would have found solution already - expr_ref linear_combination(m); - compute_linear_combination(coefficients, literals, linear_combination); - + SASSERT(!coeff_lits.empty()); // since then previous outer loop would have found solution already + expr_ref linear_combination = compute_linear_combination(coeff_lits); + m_learner.add_lemma_to_core(linear_combination); } return; @@ -586,7 +487,7 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector todo; - SASSERT(m_learner.is_a_marked(step)); - SASSERT(m_learner.is_b_marked(step)); + SASSERT(m_learner.m_pr.is_a_marked(step)); + SASSERT(m_learner.m_pr.is_b_marked(step)); SASSERT(m.get_num_parents(step) > 0); SASSERT(!m_learner.is_closed(step)); todo.push_back(step); @@ -621,7 +522,7 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector& todo) { bool is_sink = true; - ast_manager &m = m_learner.m; - ptr_vector todo_subproof; + ptr_buffer todo_subproof; - for (unsigned i = 0, sz = m.get_num_parents(step); i < sz; ++i) - { - proof* premise = m.get_parent (step, i); - { - if (m_learner.is_b_marked(premise)) - { - todo_subproof.push_back(premise); - } + for (proof* premise : m.get_parents(step)) { + if (m_learner.m_pr.is_b_marked(premise)) { + todo_subproof.push_back(premise); } } while (!todo_subproof.empty()) @@ -655,19 +550,19 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vectorget_arg(i))); - proof* premise = m.get_parent (current, i); + for (proof* premise : m.get_parents(current)) { todo_subproof.push_back(premise); } } @@ -707,6 +599,8 @@ void unsat_core_plugin_farkas_lemma::compute_linear_combination(const vector j (only relevant if i is the supersource)) + if (!(i == nullptr && m_connected_to_s.is_marked(j))) + { + m_min_cut.add_edge(node_i, node_j, 1); + } + + if (i == nullptr) + { + m_connected_to_s.mark(j, true); + } } /* * computes min-cut on the graph constructed by compute_partial_core-method * and adds the corresponding lemmas to the core */ - void unsat_core_plugin_min_cut::finalize() - { + void unsat_core_plugin_min_cut::finalize() { unsigned_vector cut_nodes; m_min_cut.compute_min_cut(cut_nodes); - - for (unsigned cut_node : cut_nodes) - { + + for (unsigned cut_node : cut_nodes) { m_learner.add_lemma_to_core(m_node_to_formula[cut_node]); - } - } + } + } } diff --git a/src/muz/spacer/spacer_unsat_core_plugin.h b/src/muz/spacer/spacer_unsat_core_plugin.h index 2ea4b4b51..c05bcc5b9 100644 --- a/src/muz/spacer/spacer_unsat_core_plugin.h +++ b/src/muz/spacer/spacer_unsat_core_plugin.h @@ -23,84 +23,73 @@ Revision History: namespace spacer { -class unsat_core_learner; + class unsat_core_learner; -class unsat_core_plugin { - -public: - unsat_core_plugin(unsat_core_learner& learner) : m_learner(learner){}; - virtual ~unsat_core_plugin(){}; - virtual void compute_partial_core(proof* step) = 0; - virtual void finalize(){}; - - unsat_core_learner& m_learner; -}; - - -class unsat_core_plugin_lemma : public unsat_core_plugin { - -public: - unsat_core_plugin_lemma(unsat_core_learner& learner) : unsat_core_plugin(learner){}; - - void compute_partial_core(proof* step) override; - -private: - void add_lowest_split_to_core(proof* step) const; -}; - - -class unsat_core_plugin_farkas_lemma : public unsat_core_plugin { - -public: - unsat_core_plugin_farkas_lemma(unsat_core_learner& learner, bool split_literals, bool use_constant_from_a=true) : unsat_core_plugin(learner), m_split_literals(split_literals), m_use_constant_from_a(use_constant_from_a) {}; - - void compute_partial_core(proof* step) override; - -private: - bool m_split_literals; - bool m_use_constant_from_a; - /* - * compute linear combination of literals 'literals' having coefficients 'coefficients' and save result in res - */ - void compute_linear_combination(const vector& coefficients, const ptr_vector& literals, expr_ref& res); -}; - - class unsat_core_plugin_farkas_lemma_optimized : public unsat_core_plugin { - - public: - unsat_core_plugin_farkas_lemma_optimized(unsat_core_learner& learner, ast_manager& m) : unsat_core_plugin(learner), m(m) {}; - - void compute_partial_core(proof* step) override; - void finalize() override; - + class unsat_core_plugin { protected: - vector > > m_linear_combinations; + typedef vector> coeff_lits_t; ast_manager& m; + public: + unsat_core_plugin(unsat_core_learner& learner); + virtual ~unsat_core_plugin() {}; + virtual void compute_partial_core(proof* step) = 0; + virtual void finalize(){}; + + unsat_core_learner& m_learner; + }; + + class unsat_core_plugin_lemma : public unsat_core_plugin { + public: + unsat_core_plugin_lemma(unsat_core_learner& learner) : unsat_core_plugin(learner){}; + void compute_partial_core(proof* step) override; + private: + void add_lowest_split_to_core(proof* step) const; + }; + + class unsat_core_plugin_farkas_lemma : public unsat_core_plugin { + public: + unsat_core_plugin_farkas_lemma(unsat_core_learner& learner, + bool split_literals, + bool use_constant_from_a=true) : + unsat_core_plugin(learner), + m_split_literals(split_literals), + m_use_constant_from_a(use_constant_from_a) {}; + void compute_partial_core(proof* step) override; + private: + bool m_split_literals; + bool m_use_constant_from_a; /* * compute linear combination of literals 'literals' having coefficients 'coefficients' and save result in res */ - void compute_linear_combination(const vector& coefficients, const ptr_vector& literals, expr_ref& res); + expr_ref compute_linear_combination(const coeff_lits_t& coeff_lits); + }; + + class unsat_core_plugin_farkas_lemma_optimized : public unsat_core_plugin { + public: + unsat_core_plugin_farkas_lemma_optimized(unsat_core_learner& learner, ast_manager& m) : unsat_core_plugin(learner) {}; + void compute_partial_core(proof* step) override; + void finalize() override; + protected: + vector m_linear_combinations; + /* + * compute linear combination of literals 'literals' having coefficients 'coefficients' and save result in res + */ + expr_ref compute_linear_combination(const coeff_lits_t& coeff_lits); }; class unsat_core_plugin_farkas_lemma_bounded : public unsat_core_plugin_farkas_lemma_optimized { - public: unsat_core_plugin_farkas_lemma_bounded(unsat_core_learner& learner, ast_manager& m) : unsat_core_plugin_farkas_lemma_optimized(learner, m) {}; - void finalize() override; }; class unsat_core_plugin_min_cut : public unsat_core_plugin { - public: unsat_core_plugin_min_cut(unsat_core_learner& learner, ast_manager& m); - void compute_partial_core(proof* step) override; void finalize() override; private: - ast_manager& m; - ast_mark m_visited; // saves for each node i whether the subproof with root i has already been added to the min-cut-problem obj_map m_proof_to_node_minus; // maps proof-steps to the corresponding minus-nodes (the ones which are closer to source) obj_map m_proof_to_node_plus; // maps proof-steps to the corresponding plus-nodes (the ones which are closer to sink) @@ -108,6 +97,7 @@ private: void add_edge(proof* i, proof* j); vector m_node_to_formula; // maps each node to the corresponding formula in the original proof + ast_mark m_connected_to_s; // remember which nodes have already been connected to the supersource, in order to avoid multiple edges. min_cut m_min_cut; }; diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index 8b8da8a69..aaa203c5e 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -9,6 +9,7 @@ Abstract: Utility functions for SPACER. + Author: Krystof Hoder (t-khoder) 2011-8-19. @@ -63,442 +64,73 @@ Notes: #include "tactic/arith/propagate_ineqs_tactic.h" #include "tactic/arith/arith_bounds_tactic.h" -#include "ast/factor_equivs.h" +#include "ast/rewriter/factor_equivs.h" +#include "qe/qe_term_graph.h" namespace spacer { - ///////////////////////// - // model_evaluator_util - // - - model_evaluator_util::model_evaluator_util(ast_manager& m) : - m(m), m_mev(nullptr) { - reset (nullptr); + void subst_vars(ast_manager& m, + app_ref_vector const& vars, model& mdl, expr_ref& fml) { + model::scoped_model_completion _sc_(mdl, true); + expr_safe_replace sub(m); + for (app * v : vars) sub.insert (v, mdl(v)); + sub(fml); } - model_evaluator_util::~model_evaluator_util() {reset (nullptr);} + void to_mbp_benchmark(std::ostream &out, expr* fml, const app_ref_vector &vars) { + ast_manager &m = vars.m(); + ast_pp_util pp(m); + pp.collect(fml); + pp.display_decls(out); + out << "(define-fun mbp_benchmark_fml () Bool\n "; + out << mk_pp(fml, m) << ")\n\n"; - void model_evaluator_util::reset(model* model) { - if (m_mev) { - dealloc(m_mev); - m_mev = nullptr; - } - m_model = model; - if (!m_model) { return; } - m_mev = alloc(model_evaluator, *m_model); - } - - bool model_evaluator_util::eval(expr *e, expr_ref &result, bool model_completion) { - m_mev->set_model_completion (model_completion); - try { - m_mev->operator() (e, result); - return true; - } - catch (model_evaluator_exception &ex) { - (void)ex; - TRACE("spacer_model_evaluator", tout << ex.msg () << "\n";); - return false; - } - } - - bool model_evaluator_util::eval(const expr_ref_vector &v, - expr_ref& res, bool model_completion) { - expr_ref e(m); - e = mk_and (v); - return eval(e, res, model_completion); - } - - - bool model_evaluator_util::is_true(const expr_ref_vector &v) { - expr_ref res(m); - return eval (v, res, false) && m.is_true (res); - } - - bool model_evaluator_util::is_false(expr *x) { - expr_ref res(m); - return eval(x, res, false) && m.is_false (res); + out << "(push)\n" + << "(assert mbp_benchmark_fml)\n" + << "(check-sat)\n" + << "(mbp mbp_benchmark_fml ("; + for (auto v : vars) {out << mk_pp(v, m) << " ";} + out << "))\n" + << "(pop)\n" + << "(exit)\n"; } - bool model_evaluator_util::is_true(expr *x) { - expr_ref res(m); - return eval(x, res, false) && m.is_true (res); - } - - void reduce_disequalities(model& model, unsigned threshold, expr_ref& fml) { - ast_manager& m = fml.get_manager(); - expr_ref_vector conjs(m); - flatten_and(fml, conjs); - obj_map diseqs; - expr* n, *lhs, *rhs; - for (unsigned i = 0; i < conjs.size(); ++i) { - if (m.is_not(conjs[i].get(), n) && m.is_eq(n, lhs, rhs)) { - if (!m.is_value(rhs)) { - std::swap(lhs, rhs); - } - if (!m.is_value(rhs)) { - continue; - } - diseqs.insert_if_not_there2(lhs, 0)->get_data().m_value++; - } - } - expr_substitution sub(m); - - unsigned orig_size = conjs.size(); - unsigned num_deleted = 0; - expr_ref val(m), tmp(m); - proof_ref pr(m); - pr = m.mk_asserted(m.mk_true()); - for (auto const& kv : diseqs) { - if (kv.m_value >= threshold) { - model.eval(kv.m_key, val); - sub.insert(kv.m_key, val, pr); - conjs.push_back(m.mk_eq(kv.m_key, val)); - num_deleted += kv.m_value; - } - } - if (orig_size < conjs.size()) { - scoped_ptr rep = mk_expr_simp_replacer(m); - rep->set_substitution(&sub); - for (unsigned i = 0; i < orig_size; ++i) { - tmp = conjs[i].get(); - (*rep)(tmp); - if (m.is_true(tmp)) { - conjs[i] = conjs.back(); - SASSERT(orig_size <= conjs.size()); - conjs.pop_back(); - SASSERT(orig_size <= 1 + conjs.size()); - if (i + 1 == orig_size) { - // no-op. - } - else if (orig_size <= conjs.size()) { - // no-op - } - else { - SASSERT(orig_size == 1 + conjs.size()); - --orig_size; - --i; - } - } - else { - conjs[i] = tmp; - } - } - IF_VERBOSE(2, verbose_stream() << "Deleted " << num_deleted << " disequalities " << conjs.size() << " conjuncts\n";); - } - fml = m.mk_and(conjs.size(), conjs.c_ptr()); - } - - // - // (f (if c1 (if c2 e1 e2) e3) b c) -> - // (if c1 (if c2 (f e1 b c) - - class ite_hoister { - ast_manager& m; - public: - ite_hoister(ast_manager& m): m(m) {} - - br_status mk_app_core(func_decl* f, unsigned num_args, expr* const* args, expr_ref& result) { - if (m.is_ite(f)) { - return BR_FAILED; - } - for (unsigned i = 0; i < num_args; ++i) { - expr* c, *t, *e; - if (!m.is_bool(args[i]) && m.is_ite(args[i], c, t, e)) { - expr_ref e1(m), e2(m); - ptr_vector args1(num_args, args); - args1[i] = t; - e1 = m.mk_app(f, num_args, args1.c_ptr()); - if (t == e) { - result = e1; - return BR_REWRITE1; - } - args1[i] = e; - e2 = m.mk_app(f, num_args, args1.c_ptr()); - result = m.mk_app(f, num_args, args); - result = m.mk_ite(c, e1, e2); - return BR_REWRITE3; - } - } - return BR_FAILED; - } - }; - - struct ite_hoister_cfg: public default_rewriter_cfg { - ite_hoister m_r; - bool rewrite_patterns() const { return false; } - br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { - return m_r.mk_app_core(f, num, args, result); - } - ite_hoister_cfg(ast_manager & m, params_ref const & p):m_r(m) {} - }; - - class ite_hoister_star : public rewriter_tpl { - ite_hoister_cfg m_cfg; - public: - ite_hoister_star(ast_manager & m, params_ref const & p): - rewriter_tpl(m, false, m_cfg), - m_cfg(m, p) {} - }; - - void hoist_non_bool_if(expr_ref& fml) { - ast_manager& m = fml.get_manager(); - scoped_no_proof _sp(m); + void qe_project_z3 (ast_manager& m, app_ref_vector& vars, expr_ref& fml, + model & mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { params_ref p; - ite_hoister_star ite_rw(m, p); - expr_ref tmp(m); - ite_rw(fml, tmp); - fml = tmp; - } + p.set_bool("reduce_all_selects", reduce_all_selects); + p.set_bool("dont_sub", dont_sub); - class test_diff_logic { - ast_manager& m; - arith_util a; - bv_util bv; - bool m_is_dl; - bool m_test_for_utvpi; - - bool is_numeric(expr* e) const { - if (a.is_numeral(e)) { - return true; - } - expr* cond, *th, *el; - if (m.is_ite(e, cond, th, el)) { - return is_numeric(th) && is_numeric(el); - } - return false; - } - - bool is_arith_expr(expr *e) const { - return is_app(e) && a.get_family_id() == to_app(e)->get_family_id(); - } - - bool is_offset(expr* e) const { - if (a.is_numeral(e)) { - return true; - } - expr* cond, *th, *el, *e1, *e2; - if (m.is_ite(e, cond, th, el)) { - return is_offset(th) && is_offset(el); - } - // recognize offsets. - if (a.is_add(e, e1, e2)) { - if (is_numeric(e1)) { - return is_offset(e2); - } - if (is_numeric(e2)) { - return is_offset(e1); - } - return false; - } - if (m_test_for_utvpi) { - if (a.is_mul(e, e1, e2)) { - if (is_minus_one(e1)) { - return is_offset(e2); - } - if (is_minus_one(e2)) { - return is_offset(e1); - } - } - } - return !is_arith_expr(e); - } - - bool is_minus_one(expr const * e) const { - rational r; - return a.is_numeral(e, r) && r.is_minus_one(); - } - - bool test_ineq(expr* e) const { - SASSERT(a.is_le(e) || a.is_ge(e) || m.is_eq(e)); - SASSERT(to_app(e)->get_num_args() == 2); - expr * lhs = to_app(e)->get_arg(0); - expr * rhs = to_app(e)->get_arg(1); - if (is_offset(lhs) && is_offset(rhs)) - { return true; } - if (!is_numeric(rhs)) - { std::swap(lhs, rhs); } - if (!is_numeric(rhs)) - { return false; } - // lhs can be 'x' or '(+ x (* -1 y))' - if (is_offset(lhs)) - { return true; } - expr* arg1, *arg2; - if (!a.is_add(lhs, arg1, arg2)) - { return false; } - // x - if (m_test_for_utvpi) { - return is_offset(arg1) && is_offset(arg2); - } - if (is_arith_expr(arg1)) - { std::swap(arg1, arg2); } - if (is_arith_expr(arg1)) - { return false; } - // arg2: (* -1 y) - expr* m1, *m2; - if (!a.is_mul(arg2, m1, m2)) - { return false; } - return is_minus_one(m1) && is_offset(m2); - } - - bool test_eq(expr* e) const { - expr* lhs, *rhs; - VERIFY(m.is_eq(e, lhs, rhs)); - if (!a.is_int_real(lhs)) { - return true; - } - if (a.is_numeral(lhs) || a.is_numeral(rhs)) { - return test_ineq(e); - } - return - test_term(lhs) && - test_term(rhs) && - !a.is_mul(lhs) && - !a.is_mul(rhs); - } - - bool test_term(expr* e) const { - if (m.is_bool(e)) { - return true; - } - if (a.is_numeral(e)) { - return true; - } - if (is_offset(e)) { - return true; - } - expr* lhs, *rhs; - if (a.is_add(e, lhs, rhs)) { - if (!a.is_numeral(lhs)) { - std::swap(lhs, rhs); - } - return a.is_numeral(lhs) && is_offset(rhs); - } - if (a.is_mul(e, lhs, rhs)) { - return is_minus_one(lhs) || is_minus_one(rhs); - } - return false; - } - - bool is_non_arith_or_basic(expr* e) - { - if (!is_app(e)) { - return false; - } - family_id fid = to_app(e)->get_family_id(); - - if (fid == null_family_id && - !m.is_bool(e) && - to_app(e)->get_num_args() > 0) { - return true; - } - return - fid != m.get_basic_family_id() && - fid != null_family_id && - fid != a.get_family_id() && - fid != bv.get_family_id(); - } - - public: - test_diff_logic(ast_manager& m): m(m), a(m), bv(m), m_is_dl(true), m_test_for_utvpi(false) {} - - void test_for_utvpi() { m_test_for_utvpi = true; } - - void operator()(expr* e) - { - if (!m_is_dl) { - return; - } - if (a.is_le(e) || a.is_ge(e)) { - m_is_dl = test_ineq(e); - } else if (m.is_eq(e)) { - m_is_dl = test_eq(e); - } else if (is_non_arith_or_basic(e)) { - m_is_dl = false; - } else if (is_app(e)) { - app* a = to_app(e); - for (unsigned i = 0; m_is_dl && i < a->get_num_args(); ++i) { - m_is_dl = test_term(a->get_arg(i)); - } - } - - if (!m_is_dl) { - char const* msg = "non-diff: "; - if (m_test_for_utvpi) { - msg = "non-utvpi: "; - } - IF_VERBOSE(1, verbose_stream() << msg << mk_pp(e, m) << "\n";); - } - } - - bool is_dl() const { return m_is_dl; } - }; - -bool is_difference_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls) -{ - test_diff_logic test(m); - expr_fast_mark1 mark; - for (unsigned i = 0; i < num_fmls; ++i) { - quick_for_each_expr(test, mark, fmls[i]); - } - return test.is_dl(); - } - -bool is_utvpi_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls) -{ - test_diff_logic test(m); - test.test_for_utvpi(); - expr_fast_mark1 mark; - for (unsigned i = 0; i < num_fmls; ++i) { - quick_for_each_expr(test, mark, fmls[i]); - } - return test.is_dl(); - } - - - void subst_vars (ast_manager& m, app_ref_vector const& vars, - model* M, expr_ref& fml) -{ - expr_safe_replace sub (m); - model_evaluator_util mev (m); - mev.set_model(*M); - for (unsigned i = 0; i < vars.size (); i++) { - app* v = vars.get (i); - expr_ref val (m); - VERIFY(mev.eval (v, val, true)); - sub.insert (v, val); - } - sub (fml); + qe::mbp mbp(m, p); + mbp.spacer(vars, mdl, fml); } /* * eliminate simple equalities using qe_lite * then, MBP for Booleans (substitute), reals (based on LW), ints (based on Cooper), and arrays */ -void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, - const model_ref& M, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) -{ + void qe_project_spacer (ast_manager& m, app_ref_vector& vars, expr_ref& fml, + model& mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { th_rewriter rw (m); TRACE ("spacer_mbp", - tout << "Before projection:\n"; - tout << mk_pp (fml, m) << "\n"; - tout << "Vars:\n"; - for (unsigned i = 0; i < vars.size(); ++i) { - tout << mk_pp(vars.get (i), m) << "\n"; - } - ); + tout << "Before projection:\n"; + tout << fml << "\n"; + tout << "Vars:\n" << vars;); { - // Ensure that top-level AND of fml is flat - expr_ref_vector flat(m); - flatten_and (fml, flat); - if (flat.size () == 1) - { fml = flat.get(0); } - else if (flat.size () > 1) - { fml = m.mk_and(flat.size(), flat.c_ptr()); } + // Ensure that top-level AND of fml is flat + expr_ref_vector flat(m); + flatten_and (fml, flat); + fml = mk_and(flat); } + + // uncomment for benchmarks + //to_mbp_benchmark(verbose_stream(), fml, vars); + app_ref_vector arith_vars (m); app_ref_vector array_vars (m); array_util arr_u (m); @@ -507,56 +139,45 @@ void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, expr_ref bval (m); while (true) { - params_ref p; - qe_lite qe(m, p, false); + params_ref p; + qe_lite qe(m, p, false); qe (vars, fml); rw (fml); TRACE ("spacer_mbp", - tout << "After qe_lite:\n"; - tout << mk_pp (fml, m) << "\n"; - tout << "Vars:\n"; - for (unsigned i = 0; i < vars.size(); ++i) { - tout << mk_pp(vars.get (i), m) << "\n"; - } - ); + tout << "After qe_lite:\n"; + tout << mk_pp (fml, m) << "\n"; + tout << "Vars:\n" << vars;); + SASSERT (!m.is_false (fml)); - bool has_bool_vars = false; // sort out vars into bools, arith (int/real), and arrays - for (unsigned i = 0; i < vars.size (); i++) { - if (m.is_bool (vars.get (i))) { - // obtain the interpretation of the ith var using model completion - VERIFY (M->eval (vars.get (i), bval, true)); - bool_sub.insert (vars.get (i), bval); - has_bool_vars = true; - } else if (arr_u.is_array(vars.get(i))) { - array_vars.push_back (vars.get (i)); - } else { - SASSERT (ari_u.is_int (vars.get (i)) || ari_u.is_real (vars.get (i))); - arith_vars.push_back (vars.get (i)); + for (app* v : vars) { + if (m.is_bool (v)) { + // obtain the interpretation of the ith var + // using model completion + model::scoped_model_completion _sc_(mdl, true); + bool_sub.insert (v, mdl(v)); + } else if (arr_u.is_array(v)) { + array_vars.push_back(v); + } else { + SASSERT (ari_u.is_int(v) || ari_u.is_real(v)); + arith_vars.push_back(v); } } // substitute Booleans - if (has_bool_vars) { - bool_sub (fml); + if (!bool_sub.empty()) { + bool_sub(fml); // -- bool_sub is not simplifying rw (fml); - SASSERT (!m.is_false (fml)); - TRACE ("spacer_mbp", - tout << "Projected Booleans:\n" << mk_pp (fml, m) << "\n"; - ); - bool_sub.reset (); + SASSERT(!m.is_false (fml)); + TRACE("spacer_mbp", tout << "Projected Booleans:\n" << fml << "\n"; ); + bool_sub.reset(); } - TRACE ("spacer_mbp", - tout << "Array vars:\n"; - for (unsigned i = 0; i < array_vars.size (); ++i) { - tout << mk_pp (array_vars.get (i), m) << "\n"; - } - ); + TRACE ("spacer_mbp", tout << "Array vars:\n"; tout << array_vars;); vars.reset (); @@ -565,70 +186,51 @@ void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, scoped_no_proof _sp (m); // -- local rewriter that is aware of current proof mode th_rewriter srw(m); - qe::array_project (*M.get (), array_vars, fml, vars, reduce_all_selects); + spacer_qe::array_project (mdl, array_vars, fml, vars, reduce_all_selects); SASSERT (array_vars.empty ()); srw (fml); SASSERT (!m.is_false (fml)); } TRACE ("spacer_mbp", - tout << "extended model:\n"; - model_pp (tout, *M); - tout << "Auxiliary variables of index and value sorts:\n"; - for (unsigned i = 0; i < vars.size (); i++) { - tout << mk_pp (vars.get (i), m) << "\n"; - } - ); + tout << "extended model:\n"; + model_pp (tout, mdl); + tout << "Auxiliary variables of index and value sorts:\n"; + tout << vars;); - if (vars.empty()) { break; } + if (vars.empty()) { break; } } // project reals and ints if (!arith_vars.empty ()) { - TRACE ("spacer_mbp", - tout << "Arith vars:\n"; - for (unsigned i = 0; i < arith_vars.size (); ++i) { - tout << mk_pp (arith_vars.get (i), m) << "\n"; - } - ); - - // XXX Does not seem to have an effect - // qe_lite qe(m); - // qe (arith_vars, fml); - // TRACE ("spacer_mbp", - // tout << "After second qelite: " << - // mk_pp (fml, m) << "\n";); + TRACE ("spacer_mbp", tout << "Arith vars:\n" << arith_vars;); if (use_native_mbp) { qe::mbp mbp (m); expr_ref_vector fmls(m); flatten_and (fml, fmls); - mbp (true, arith_vars, *M.get (), fmls); - fml = mk_and (fmls); - SASSERT (arith_vars.empty ()); + mbp (true, arith_vars, mdl, fmls); + fml = mk_and(fmls); + SASSERT(arith_vars.empty ()); } else { scoped_no_proof _sp (m); - qe::arith_project (*M.get (), arith_vars, fml); - } + spacer_qe::arith_project (mdl, arith_vars, fml); + } TRACE ("spacer_mbp", - tout << "Projected arith vars:\n" << mk_pp (fml, m) << "\n"; - tout << "Remaining arith vars:\n"; - for (unsigned i = 0; i < arith_vars.size (); i++) { - tout << mk_pp (arith_vars.get (i), m) << "\n"; - } - ); + tout << "Projected arith vars:\n" << fml << "\n"; + tout << "Remaining arith vars:\n" << arith_vars << "\n";); SASSERT (!m.is_false (fml)); } if (!arith_vars.empty ()) { - mbqi_project (*M.get(), arith_vars, fml); + mbqi_project (mdl, arith_vars, fml); } // substitute any remaining arith vars if (!dont_sub && !arith_vars.empty ()) { - subst_vars (m, arith_vars, M.get(), fml); + subst_vars (m, arith_vars, mdl, fml); TRACE ("spacer_mbp", tout << "After substituting remaining arith vars:\n"; tout << mk_pp (fml, m) << "\n"; @@ -638,16 +240,15 @@ void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, } DEBUG_CODE ( - model_evaluator_util mev (m); - expr_ref v(m); - mev.set_model(*M.get()); - SASSERT (mev.eval (fml, v, false)); - SASSERT (m.is_true (v)); + model_evaluator mev(mdl); + mev.set_model_completion(false); + SASSERT(mev.is_true(fml)); ); vars.reset (); - if (dont_sub && !arith_vars.empty ()) - { vars.append(arith_vars); } + if (dont_sub && !arith_vars.empty ()) { + vars.append(arith_vars); + } } @@ -655,18 +256,28 @@ void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, ptr_vector const& acc, unsigned j, func_decl* f, - expr* c) -{ + expr* c) + { if (is_app(c) && to_app(c)->get_decl() == f) { return to_app(c)->get_arg(j); - } else { + } else { return m.mk_app(acc[j], c); } } -void expand_literals(ast_manager &m, expr_ref_vector& conjs) -{ - if (conjs.empty()) { return; } + void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, + model &mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { + if (use_native_mbp) + qe_project_z3(m, vars, fml, mdl, + reduce_all_selects, use_native_mbp, dont_sub); + else + qe_project_spacer(m, vars, fml, mdl, + reduce_all_selects, use_native_mbp, dont_sub); + } + + void expand_literals(ast_manager &m, expr_ref_vector& conjs) { + if (conjs.empty()) { return; } arith_util arith(m); datatype_util dt(m); bv_util bv(m); @@ -674,11 +285,7 @@ void expand_literals(ast_manager &m, expr_ref_vector& conjs) rational r; unsigned bv_size; - TRACE("spacer_expand", - tout << "begin expand\n"; - for (unsigned i = 0; i < conjs.size(); ++i) { - tout << mk_pp(conjs[i].get(), m) << "\n"; - }); + TRACE("spacer_expand", tout << "begin expand\n" << conjs << "\n";); for (unsigned i = 0; i < conjs.size(); ++i) { expr* e = conjs[i].get(); @@ -686,13 +293,13 @@ void expand_literals(ast_manager &m, expr_ref_vector& conjs) conjs[i] = arith.mk_le(e1,e2); if (i+1 == conjs.size()) { conjs.push_back(arith.mk_ge(e1, e2)); - } else { + } else { conjs.push_back(conjs[i+1].get()); conjs[i+1] = arith.mk_ge(e1, e2); } ++i; - } else if ((m.is_eq(e, c, val) && is_app(val) && dt.is_constructor(to_app(val))) || - (m.is_eq(e, val, c) && is_app(val) && dt.is_constructor(to_app(val)))){ + } else if ((m.is_eq(e, c, val) && is_app(val) && dt.is_constructor(to_app(val))) || + (m.is_eq(e, val, c) && is_app(val) && dt.is_constructor(to_app(val)))) { func_decl* f = to_app(val)->get_decl(); func_decl* r = dt.get_constructor_is(f); conjs[i] = m.mk_app(r, c); @@ -700,12 +307,11 @@ void expand_literals(ast_manager &m, expr_ref_vector& conjs) for (unsigned j = 0; j < acc.size(); ++j) { conjs.push_back(m.mk_eq(apply_accessor(m, acc, j, f, c), to_app(val)->get_arg(j))); } - } else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || - (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { + } else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || + (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { rational two(2); for (unsigned j = 0; j < bv_size; ++j) { parameter p(j); - //expr* e = m.mk_app(bv.get_family_id(), OP_BIT2BOOL, 1, &p, 1, &c); expr* e = m.mk_eq(m.mk_app(bv.get_family_id(), OP_BIT1), bv.mk_extract(j, j, c)); if ((r % two).is_zero()) { e = m.mk_not(e); @@ -713,404 +319,416 @@ void expand_literals(ast_manager &m, expr_ref_vector& conjs) r = div(r, two); if (j == 0) { conjs[i] = e; - } else { + } else { conjs.push_back(e); } } } } - TRACE("spacer_expand", - tout << "end expand\n"; - for (unsigned i = 0; i < conjs.size(); ++i) { - tout << mk_pp(conjs[i].get(), m) << "\n"; - }); + TRACE("spacer_expand", tout << "end expand\n" << conjs << "\n";); } namespace { -class implicant_picker { - model_evaluator_util &m_mev; + class implicant_picker { + model &m_model; ast_manager &m; arith_util m_arith; expr_ref_vector m_todo; expr_mark m_visited; + // add literal to the implicant + // applies lightweight normalization + void add_literal(expr *e, expr_ref_vector &out) { + SASSERT(m.is_bool(e)); - void add_literal (expr *e, expr_ref_vector &out) - { - SASSERT (m.is_bool (e)); + expr_ref res(m), v(m); + v = m_model(e); + // the literal must have a value + SASSERT(m.is_true(v) || m.is_false(v)); - expr_ref res (m), v(m); - m_mev.eval (e, v, false); - SASSERT (m.is_true (v) || m.is_false (v)); + res = m.is_false(v) ? m.mk_not(e) : e; - res = m.is_false (v) ? m.mk_not (e) : e; - - if (m.is_distinct (res)) { - // -- (distinct a b) == (not (= a b)) + if (m.is_distinct(res)) { + // --(distinct a b) == (not (= a b)) if (to_app(res)->get_num_args() == 2) { - res = m.mk_eq (to_app(res)->get_arg(0), to_app(res)->get_arg(1)); - res = m.mk_not (res); + res = m.mk_eq(to_app(res)->get_arg(0), + to_app(res)->get_arg(1)); + res = m.mk_not(res); } } - expr *nres, *f1, *f2; + expr *nres = nullptr, *f1 = nullptr, *f2 = nullptr; if (m.is_not(res, nres)) { - // -- (not (xor a b)) == (= a b) + // --(not (xor a b)) == (= a b) if (m.is_xor(nres, f1, f2)) - { res = m.mk_eq(f1, f2); } - + res = m.mk_eq(f1, f2); // -- split arithmetic inequality - else if (m.is_eq (nres, f1, f2) && m_arith.is_int_real (f1)) { - expr_ref u(m); - u = m_arith.mk_lt(f1, f2); - if (m_mev.eval (u, v, false) && m.is_true (v)) - { res = u; } - else - { res = m_arith.mk_lt(f2, f1); } + else if (m.is_eq(nres, f1, f2) && m_arith.is_int_real(f1)) { + res = m_arith.mk_lt(f1, f2); + if (!m_model.is_true(res)) + res = m_arith.mk_lt(f2, f1); } } - if (!m_mev.is_true (res)) - { verbose_stream() << "Bad literal: " << mk_pp(res, m) << "\n"; } - SASSERT (m_mev.is_true (res)); - out.push_back (res); + if (!m_model.is_true(res)) { + verbose_stream() << "Bad literal: " << res << "\n"; + } + SASSERT(m_model.is_true(res)); + out.push_back(res); } - void process_app(app *a, expr_ref_vector &out) - { - if (m_visited.is_marked(a)) { return; } - SASSERT (m.is_bool (a)); + void process_app(app *a, expr_ref_vector &out) { + if (m_visited.is_marked(a)) return; + SASSERT(m.is_bool(a)); expr_ref v(m); - m_mev.eval (a, v, false); + v = m_model(a); + bool is_true = m.is_true(v); - if (!m.is_true(v) && !m.is_false(v)) { return; } + if (!is_true && !m.is_false(v)) return; - expr *na, *f1, *f2, *f3; + expr *na = nullptr, *f1 = nullptr, *f2 = nullptr, *f3 = nullptr; - if (a->get_family_id() != m.get_basic_family_id()) - { add_literal(a, out); } - else if (is_uninterp_const(a)) - { add_literal(a, out); } - else if (m.is_not(a, na) && m.is_not(na, na)) - { m_todo.push_back(na); } - else if (m.is_not(a, na)) - { m_todo.push_back(na); } + SASSERT(!m.is_false(a)); + if (m.is_true(a)) { + // noop + } + else if (a->get_family_id() != m.get_basic_family_id()) { + add_literal(a, out); + } + else if (is_uninterp_const(a)) { + add_literal(a, out); + } + else if (m.is_not(a, na)) { + m_todo.push_back(na); + } else if (m.is_distinct(a)) { - if (m.is_false(v)) - m_todo.push_back - (qe::project_plugin::pick_equality(m, *m_mev.get_model(), a)); - else if (a->get_num_args() == 2) - { add_literal(a, out); } - else + if (!is_true) { + f1 = qe::project_plugin::pick_equality(m, m_model, a); + m_todo.push_back(f1); + } + else if (a->get_num_args() == 2) { + add_literal(a, out); + } + else { m_todo.push_back(m.mk_distinct_expanded(a->get_num_args(), a->get_args())); - } else if (m.is_and(a)) { - if (m.is_true(v)) - { m_todo.append(a->get_num_args(), a->get_args()); } - else if (m.is_false(v)) { - for (unsigned i = 0, sz = a->get_num_args (); i < sz; ++i) { - if (m_mev.is_false(a->get_arg(i))) { - m_todo.push_back(a->get_arg(i)); + } + } + else if (m.is_and(a)) { + if (is_true) { + m_todo.append(a->get_num_args(), a->get_args()); + } + else { + for (expr* e : *a) { + if (m_model.is_false(e)) { + m_todo.push_back(e); break; } } } - } else if (m.is_or(a)) { - if (m.is_false(v)) - { m_todo.append(a->get_num_args(), a->get_args()); } - else if (m.is_true(v)) { - for (unsigned i = 0, sz = a->get_num_args(); i < sz; ++i) { - if (m_mev.is_true(a->get_arg(i))) { - m_todo.push_back(a->get_arg(i)); + } + else if (m.is_or(a)) { + if (!is_true) + m_todo.append(a->get_num_args(), a->get_args()); + else { + for (expr * e : *a) { + if (m_model.is_true(e)) { + m_todo.push_back(e); break; } } } - } else if (m.is_iff(a, f1, f2) || m.is_eq(a, f1, f2) || - (m.is_true(v) && m.is_not(a, na) && m.is_xor (na, f1, f2))) { + } + else if (m.is_eq(a, f1, f2) || + (is_true && m.is_not(a, na) && m.is_xor(na, f1, f2))) { if (!m.are_equal(f1, f2) && !m.are_distinct(f1, f2)) { if (m.is_bool(f1) && (!is_uninterp_const(f1) || !is_uninterp_const(f2))) - { m_todo.append(a->get_num_args(), a->get_args()); } + m_todo.append(a->get_num_args(), a->get_args()); else - { add_literal(a, out); } + add_literal(a, out); } - } else if (m.is_ite(a, f1, f2, f3)) { - if (m.are_equal(f2, f3)) { m_todo.push_back(f2); } - else if (m_mev.is_true (f2) && m_mev.is_true (f3)) { - m_todo.push_back(f2); - m_todo.push_back(f3); - } else if (m_mev.is_false(f2) && m_mev.is_false(f3)) { - m_todo.push_back(f2); - m_todo.push_back(f3); - } else if (m_mev.is_true(f1)) { - m_todo.push_back(f1); - m_todo.push_back(f2); - } else if (m_mev.is_false(f1)) { - m_todo.push_back(f1); - m_todo.push_back(f3); + } + else if (m.is_ite(a, f1, f2, f3)) { + if (m.are_equal(f2, f3)) { + m_todo.push_back(f2); } - } else if (m.is_xor(a, f1, f2)) - { m_todo.append(a->get_num_args(), a->get_args()); } + else if (m_model.is_true(f2) && m_model.is_true(f3)) { + m_todo.push_back(f2); + m_todo.push_back(f3); + } + else if (m_model.is_false(f2) && m_model.is_false(f3)) { + m_todo.push_back(f2); + m_todo.push_back(f3); + } + else if (m_model.is_true(f1)) { + m_todo.push_back(f1); + m_todo.push_back(f2); + } + else if (m_model.is_false(f1)) { + m_todo.push_back(f1); + m_todo.push_back(f3); + } + } + else if (m.is_xor(a, f1, f2)) { + m_todo.append(a->get_num_args(), a->get_args()); + } else if (m.is_implies(a, f1, f2)) { - if (m.is_true (v)) { - if (m_mev.is_true(f2)) { m_todo.push_back(f2); } - else if (m_mev.is_false(f1)) { m_todo.push_back(f1); } - } else if (m.is_false(v)) - { m_todo.append(a->get_num_args(), a->get_args()); } - } else if (m.is_true(a) || m.is_false(a)) { /* nothing */ } + if (is_true) { + if (m_model.is_true(f2)) + m_todo.push_back(f2); + else if (m_model.is_false(f1)) + m_todo.push_back(f1); + } + else + m_todo.append(a->get_num_args(), a->get_args()); + } else { - verbose_stream () << "Unexpected expression: " - << mk_pp(a, m) << "\n"; + IF_VERBOSE(0, + verbose_stream() << "Unexpected expression: " + << mk_pp(a, m) << "\n"); UNREACHABLE(); } } - void pick_literals(expr *e, expr_ref_vector &out) - { + void pick_literals(expr *e, expr_ref_vector &out) { SASSERT(m_todo.empty()); - if (m_visited.is_marked(e)) { return; } - SASSERT(is_app(e)); + if (m_visited.is_marked(e) || !is_app(e)) return; m_todo.push_back(e); do { - app *a = to_app(m_todo.back()); + e = m_todo.back(); + if (!is_app(e)) continue; + app * a = to_app(e); m_todo.pop_back(); process_app(a, out); m_visited.mark(a, true); - } while (!m_todo.empty()); + } + while (!m_todo.empty()); } - bool pick_implicant(const expr_ref_vector &in, expr_ref_vector &out) - { + bool pick_implicant(const expr_ref_vector &in, expr_ref_vector &out) { m_visited.reset(); - expr_ref e(m); - e = mk_and (in); - bool is_true = m_mev.is_true (e); + bool is_true = m_model.is_true(in); - for (unsigned i = 0, sz = in.size (); i < sz; ++i) { - if (is_true || m_mev.is_true(in.get(i))) - { pick_literals(in.get(i), out); } + for (expr* e : in) { + if (is_true || m_model.is_true(e)) { + pick_literals(e, out); + } } - - m_visited.reset (); + m_visited.reset(); return is_true; } public: - implicant_picker (model_evaluator_util &mev) : - m_mev (mev), m (m_mev.get_ast_manager ()), m_arith(m), m_todo(m) {} - void operator() (expr_ref_vector &in, expr_ref_vector& out) - {pick_implicant (in, out);} + implicant_picker(model &mdl) : + m_model(mdl), m(m_model.get_manager()), m_arith(m), m_todo(m) {} + + void operator()(expr_ref_vector &in, expr_ref_vector& out) { + model::scoped_model_completion _sc_(m_model, false); + pick_implicant(in, out); + } }; - } - - void compute_implicant_literals (model_evaluator_util &mev, expr_ref_vector &formula, - expr_ref_vector &res) - { - // XXX what is the point of flattening? - flatten_and (formula); - if (formula.empty()) { return; } - - implicant_picker ipick (mev); - ipick (formula, res); - } - -void simplify_bounds_old(expr_ref_vector& cube) { - ast_manager& m = cube.m(); - - scoped_no_proof _no_pf_(m); - goal_ref g(alloc(goal, m, false, false, false)); - - for (unsigned i = 0; i < cube.size(); ++i) { - g->assert_expr(cube.get(i)); - } - - expr_ref tmp(m); - goal_ref_buffer result; - tactic_ref simplifier = mk_arith_bounds_tactic(m); - (*simplifier)(g, result); - SASSERT(result.size() == 1); - goal* r = result[0]; - - cube.reset(); - for (unsigned i = 0; i < r->size(); ++i) { - cube.push_back(r->form(i)); - } } -void simplify_bounds_new (expr_ref_vector &cube) { - ast_manager &m = cube.m(); + void compute_implicant_literals(model &mdl, + expr_ref_vector &formula, + expr_ref_vector &res) { + // flatten the formula and remove all trivial literals + // TBD: not clear why there is a dependence on it(other than + // not handling of Boolean constants by implicant_picker), however, + // it was a source of a problem on a benchmark + flatten_and(formula); + if (formula.empty()) {return;} - scoped_no_proof _no_pf_(m); - - goal_ref g(alloc(goal, m, false, false, false)); - for (unsigned i = 0, sz = cube.size(); i < sz; ++i) { - g->assert_expr(cube.get(i)); + implicant_picker ipick(mdl); + ipick(formula, res); } - goal_ref_buffer goals; - tactic_ref prop_values = mk_propagate_values_tactic(m); - tactic_ref prop_bounds = mk_propagate_ineqs_tactic(m); - tactic_ref t = and_then(prop_values.get(), prop_bounds.get()); + void simplify_bounds_old(expr_ref_vector& cube) { + ast_manager& m = cube.m(); + scoped_no_proof _no_pf_(m); + goal_ref g(alloc(goal, m, false, false, false)); + for (expr* c : cube) + g->assert_expr(c); - (*t)(g, goals); - SASSERT(goals.size() == 1); - - g = goals[0]; - cube.reset(); - for (unsigned i = 0; i < g->size(); ++i) { - cube.push_back(g->form(i)); + goal_ref_buffer result; + tactic_ref simplifier = mk_arith_bounds_tactic(m); + (*simplifier)(g, result); + SASSERT(result.size() == 1); + goal* r = result[0]; + cube.reset(); + for (unsigned i = 0; i < r->size(); ++i) { + cube.push_back(r->form(i)); + } } -} -void simplify_bounds(expr_ref_vector &cube) { - simplify_bounds_new(cube); -} + void simplify_bounds_new(expr_ref_vector &cube) { + ast_manager &m = cube.m(); + scoped_no_proof _no_pf_(m); + goal_ref g(alloc(goal, m, false, false, false)); + for (expr* c : cube) + g->assert_expr(c); -/// Adhoc rewriting of arithmetic expressions -struct adhoc_rewriter_cfg : public default_rewriter_cfg { - ast_manager &m; - arith_util m_util; + goal_ref_buffer goals; + tactic_ref prop_values = mk_propagate_values_tactic(m); + tactic_ref prop_bounds = mk_propagate_ineqs_tactic(m); + tactic_ref t = and_then(prop_values.get(), prop_bounds.get()); - adhoc_rewriter_cfg (ast_manager &manager) : m(manager), m_util(m) {} + (*t)(g, goals); + SASSERT(goals.size() == 1); - bool is_le(func_decl const * n) const - { return is_decl_of(n, m_util.get_family_id (), OP_LE); } - bool is_ge(func_decl const * n) const - { return is_decl_of(n, m_util.get_family_id (), OP_GE); } + g = goals[0]; + cube.reset(); + for (unsigned i = 0; i < g->size(); ++i) { + cube.push_back(g->form(i)); + } + } - br_status reduce_app (func_decl * f, unsigned num, expr * const * args, - expr_ref & result, proof_ref & result_pr) - { + void simplify_bounds(expr_ref_vector &cube) { + simplify_bounds_new(cube); + } + + /// Adhoc rewriting of arithmetic expressions + struct adhoc_rewriter_cfg : public default_rewriter_cfg { + ast_manager &m; + arith_util m_util; + + adhoc_rewriter_cfg(ast_manager &manager) : m(manager), m_util(m) {} + + bool is_le(func_decl const * n) const { return m_util.is_le(n); } + bool is_ge(func_decl const * n) const { return m_util.is_ge(n); } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, + expr_ref & result, proof_ref & result_pr) { expr * e; - br_status st = BR_FAILED; - if (is_le(f)) { - st = mk_le_core (args[0], args[1], result); - } else if (is_ge(f)) { - st = mk_ge_core (args[0], args[1], result); - } else if (m.is_not(f)) { - if (m.is_not (args[0], e)) { - result = e; - st = BR_DONE; + if (is_le(f)) + return mk_le_core(args[0], args[1], result); + if (is_ge(f)) + return mk_ge_core(args[0], args[1], result); + if (m.is_not(f) && m.is_not(args[0], e)) { + result = e; + return BR_DONE; + } + return BR_FAILED; + } + + br_status mk_le_core(expr *arg1, expr * arg2, expr_ref & result) { + // t <= -1 ==> t < 0 ==> !(t >= 0) + if (m_util.is_int(arg1) && m_util.is_minus_one(arg2)) { + result = m.mk_not(m_util.mk_ge(arg1, mk_zero())); + return BR_DONE; + } + return BR_FAILED; + } + br_status mk_ge_core(expr * arg1, expr * arg2, expr_ref & result) { + // t >= 1 ==> t > 0 ==> !(t <= 0) + if (m_util.is_int(arg1) && is_one(arg2)) { + + result = m.mk_not(m_util.mk_le(arg1, mk_zero())); + return BR_DONE; + } + return BR_FAILED; + } + expr * mk_zero() {return m_util.mk_numeral(rational(0), true);} + bool is_one(expr const * n) const { + rational val; return m_util.is_numeral(n, val) && val.is_one(); + } + }; + + void normalize(expr *e, expr_ref &out, + bool use_simplify_bounds, + bool use_factor_eqs) + { + + params_ref params; + // arith_rewriter + params.set_bool("sort_sums", true); + params.set_bool("gcd_rounding", true); + params.set_bool("arith_lhs", true); + // poly_rewriter + params.set_bool("som", true); + params.set_bool("flat", true); + + // apply rewriter + th_rewriter rw(out.m(), params); + rw(e, out); + + adhoc_rewriter_cfg adhoc_cfg(out.m()); + rewriter_tpl adhoc_rw(out.m(), false, adhoc_cfg); + adhoc_rw(out.get(), out); + + if (out.m().is_and(out)) { + expr_ref_vector v(out.m()); + flatten_and(out, v); + + if (v.size() > 1) { + // sort arguments of the top-level and + std::stable_sort(v.c_ptr(), v.c_ptr() + v.size(), ast_lt_proc()); + + if (use_simplify_bounds) { + // remove redundant inequalities + simplify_bounds(v); } + if (use_factor_eqs) { + // -- refactor equivalence classes and choose a representative + qe::term_graph egraph(out.m()); + egraph.add_lits(v); + v.reset(); + egraph.to_lits(v); + } + + TRACE("spacer_normalize", + tout << "Normalized:\n" + << out << "\n" + << "to\n" + << mk_and(v) << "\n";); + TRACE("spacer_normalize", + qe::term_graph egraph(out.m()); + for (expr* e : v) egraph.add_lit(to_app(e)); + tout << "Reduced app:\n" + << mk_pp(egraph.to_app(), out.m()) << "\n";); + out = mk_and(v); } - - return st; - } - - br_status mk_le_core (expr *arg1, expr * arg2, expr_ref & result) - { - // t <= -1 ==> t < 0 ==> ! (t >= 0) - if (m_util.is_int (arg1) && m_util.is_minus_one (arg2)) { - result = m.mk_not (m_util.mk_ge (arg1, mk_zero ())); - return BR_DONE; - } - return BR_FAILED; - } - br_status mk_ge_core (expr * arg1, expr * arg2, expr_ref & result) - { - // t >= 1 ==> t > 0 ==> ! (t <= 0) - if (m_util.is_int (arg1) && is_one (arg2)) { - - result = m.mk_not (m_util.mk_le (arg1, mk_zero ())); - return BR_DONE; - } - return BR_FAILED; - } - expr * mk_zero () {return m_util.mk_numeral (rational (0), true);} - bool is_one (expr const * n) const - {rational val; return m_util.is_numeral (n, val) && val.is_one ();} -}; - -void normalize (expr *e, expr_ref &out, - bool use_simplify_bounds, - bool use_factor_eqs) -{ - - params_ref params; - // arith_rewriter - params.set_bool ("sort_sums", true); - params.set_bool ("gcd_rounding", true); - params.set_bool ("arith_lhs", true); - // poly_rewriter - params.set_bool ("som", true); - params.set_bool ("flat", true); - - // apply rewriter - th_rewriter rw(out.m(), params); - rw (e, out); - - adhoc_rewriter_cfg adhoc_cfg(out.m ()); - rewriter_tpl adhoc_rw (out.m (), false, adhoc_cfg); - adhoc_rw (out.get (), out); - - if (out.m().is_and(out)) { - expr_ref_vector v(out.m()); - flatten_and (out, v); - - if (v.size() > 1) { - // sort arguments of the top-level and - std::stable_sort (v.c_ptr(), v.c_ptr () + v.size (), ast_lt_proc()); - - if (use_simplify_bounds) { - // remove redundant inequalities - simplify_bounds (v); - } - if (use_factor_eqs) { - // pick non-constant value representative for - // equivalence classes - expr_equiv_class eq_classes(out.m()); - factor_eqs(v, eq_classes); - equiv_to_expr(eq_classes, v); - } - - out = mk_and (v); } } -} -// rewrite term such that the pretty printing is easier to read -struct adhoc_rewriter_rpp : public default_rewriter_cfg { - ast_manager &m; - arith_util m_arith; + // rewrite term such that the pretty printing is easier to read + struct adhoc_rewriter_rpp : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; - adhoc_rewriter_rpp (ast_manager &manager) : m(manager), m_arith(m) {} + adhoc_rewriter_rpp(ast_manager &manager) : m(manager), m_arith(m) {} - bool is_le(func_decl const * n) const - { return is_decl_of(n, m_arith.get_family_id (), OP_LE); } - bool is_ge(func_decl const * n) const - { return is_decl_of(n, m_arith.get_family_id (), OP_GE); } - bool is_lt(func_decl const * n) const - { return is_decl_of(n, m_arith.get_family_id (), OP_LT); } - bool is_gt(func_decl const * n) const - { return is_decl_of(n, m_arith.get_family_id (), OP_GT); } - bool is_zero (expr const * n) const - {rational val; return m_arith.is_numeral(n, val) && val.is_zero();} + bool is_le(func_decl const * n) const { return m_arith.is_le(n); } + bool is_ge(func_decl const * n) const { return m_arith.is_ge(n); } + bool is_lt(func_decl const * n) const { return m_arith.is_lt(n); } + bool is_gt(func_decl const * n) const { return m_arith.is_gt(n); } + bool is_zero(expr const * n) const {rational val; return m_arith.is_numeral(n, val) && val.is_zero();} - br_status reduce_app (func_decl * f, unsigned num, expr * const * args, - expr_ref & result, proof_ref & result_pr) + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, + expr_ref & result, proof_ref & result_pr) { br_status st = BR_FAILED; expr *e1, *e2, *e3, *e4; - // rewrites (= (+ A (* -1 B)) 0) into (= A B) - if (m.is_eq (f) && is_zero (args [1]) && - m_arith.is_add (args[0], e1, e2) && - m_arith.is_mul (e2, e3, e4) && m_arith.is_minus_one (e3)) { - result = m.mk_eq (e1, e4); + // rewrites(=(+ A(* -1 B)) 0) into(= A B) + if (m.is_eq(f) && is_zero(args [1]) && + m_arith.is_add(args[0], e1, e2) && + m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { + result = m.mk_eq(e1, e4); return BR_DONE; } // simplify normalized leq, where right side is different from 0 - // rewrites (<= (+ A (* -1 B)) C) into (<= A B+C) + // rewrites(<=(+ A(* -1 B)) C) into(<= A B+C) else if ((is_le(f) || is_lt(f) || is_ge(f) || is_gt(f)) && - m_arith.is_add (args[0], e1, e2) && - m_arith.is_mul (e2, e3, e4) && m_arith.is_minus_one (e3)) { + m_arith.is_add(args[0], e1, e2) && + m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { expr_ref rhs(m); - rhs = is_zero (args[1]) ? e4 : m_arith.mk_add(e4, args[1]); + rhs = is_zero(args[1]) ? e4 : m_arith.mk_add(e4, args[1]); if (is_le(f)) { result = m_arith.mk_le(e1, rhs); @@ -1128,7 +746,7 @@ struct adhoc_rewriter_rpp : public default_rewriter_cfg { { UNREACHABLE(); } } // simplify negation of ordering predicate - else if (m.is_not (f)) { + else if (m.is_not(f)) { if (m_arith.is_lt(args[0], e1, e2)) { result = m_arith.mk_ge(e1, e2); st = BR_DONE; @@ -1145,228 +763,175 @@ struct adhoc_rewriter_rpp : public default_rewriter_cfg { } return st; } + }; -}; -mk_epp::mk_epp(ast *t, ast_manager &m, unsigned indent, - unsigned num_vars, char const * var_prefix) : - mk_pp (t, m, m_epp_params, indent, num_vars, var_prefix), m_epp_expr(m) { - m_epp_params.set_uint("min_alias_size", UINT_MAX); - m_epp_params.set_uint("max_depth", UINT_MAX); + mk_epp::mk_epp(ast *t, ast_manager &m, unsigned indent, + unsigned num_vars, char const * var_prefix) : + mk_pp(t, m, m_epp_params, indent, num_vars, var_prefix), m_epp_expr(m) { + m_epp_params.set_uint("min_alias_size", UINT_MAX); + m_epp_params.set_uint("max_depth", UINT_MAX); - if (is_expr (m_ast)) { - rw(to_expr(m_ast), m_epp_expr); - m_ast = m_epp_expr; - } -} - -void mk_epp::rw(expr *e, expr_ref &out) -{ - adhoc_rewriter_rpp cfg(out.m()); - rewriter_tpl arw(out.m(), false, cfg); - arw(e, out); -} - - void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars) - { - expr_free_vars fv; - ast_manager &m = out.get_manager (); - fv (e); - if (vars.size () < fv.size ()) - { vars.resize(fv.size()); } - for (unsigned i = 0, sz = fv.size (); i < sz; ++i) { - SASSERT (fv[i]); - std::string str = "zk!" + datalog::to_string(sz - 1 - i); - vars [i] = m.mk_const (symbol(str.c_str()), fv [i]); - } - var_subst vs(m); - vs (e, vars.size (), (expr**) vars.c_ptr (), out); + if (is_expr(m_ast)) { + rw(to_expr(m_ast), m_epp_expr); + m_ast = m_epp_expr; + } } + void mk_epp::rw(expr *e, expr_ref &out) { + adhoc_rewriter_rpp cfg(out.m()); + rewriter_tpl arw(out.m(), false, cfg); + arw(e, out); + } + + void ground_expr(expr *e, expr_ref &out, app_ref_vector &vars) { + expr_free_vars fv; + ast_manager &m = out.get_manager(); + + fv(e); + if (vars.size() < fv.size()) { + vars.resize(fv.size()); + } + for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { + sort *s = fv[i] ? fv[i] : m.mk_bool_sort(); + vars[i] = mk_zk_const(m, i, s); + var_subst vs(m, false); + vs(e, vars.size(),(expr * *) vars.c_ptr(), out); + } + } struct index_term_finder { - ast_manager &m; - array_util m_array; - app_ref m_var; + ast_manager &m; + array_util m_array; + app_ref m_var; expr_ref_vector &m_res; - index_term_finder (ast_manager &mgr, app* v, expr_ref_vector &res) : m(mgr), m_array (m), m_var (v, m), m_res (res) {} - void operator() (var *n) {} - void operator() (quantifier *n) {} - void operator() (app *n) - { - expr *e1, *e2; - if (m_array.is_select (n) && n->get_arg (1) != m_var) { - m_res.push_back (n->get_arg (1)); - } else if (m.is_eq(n, e1, e2)) { - if (e1 == m_var) { m_res.push_back(e2); } - else if (e2 == m_var) { m_res.push_back(e1); } + index_term_finder(ast_manager &mgr, app* v, expr_ref_vector &res) : m(mgr), m_array(m), m_var(v, m), m_res(res) {} + void operator()(var *n) {} + void operator()(quantifier *n) {} + void operator()(app *n) { + if (m_array.is_select(n) || m.is_eq(n)) { + unsigned i = 0; + for (expr * arg : *n) { + if ((m.is_eq(n) || i > 0) && m_var != arg) m_res.push_back(arg); + ++i; + } } } }; - bool mbqi_project_var (model_evaluator_util &mev, app* var, expr_ref &fml) - { - ast_manager &m = fml.get_manager (); + bool mbqi_project_var(model &mdl, app* var, expr_ref &fml) { + ast_manager &m = fml.get_manager(); + model::scoped_model_completion _sc_(mdl, false); expr_ref val(m); - mev.eval (var, val, false); + val = mdl(var); - TRACE ("mbqi_project_verbose", - tout << "MBQI: var: " << mk_pp (var, m) << "\n" - << "fml: " << mk_pp (fml, m) << "\n";); - expr_ref_vector terms (m); - index_term_finder finder (m, var, terms); - for_each_expr (finder, fml); + TRACE("mbqi_project_verbose", + tout << "MBQI: var: " << mk_pp(var, m) << "\n" + << "fml: " << fml << "\n";); + expr_ref_vector terms(m); + index_term_finder finder(m, var, terms); + for_each_expr(finder, fml); + TRACE("mbqi_project_verbose", tout << "terms:\n" << terms << "\n";); - TRACE ("mbqi_project_verbose", - tout << "terms:\n"; - for (unsigned i = 0, e = terms.size (); i < e; ++i) - tout << i << ": " << mk_pp (terms.get (i), m) << "\n"; - ); + for (expr * term : terms) { + expr_ref tval(m); + tval = mdl(term); - for (unsigned i = 0, e = terms.size(); i < e; ++i) { - expr* term = terms.get (i); - expr_ref tval (m); - mev.eval (term, tval, false); - - TRACE ("mbqi_project_verbose", - tout << "term: " << mk_pp (term, m) - << " tval: " << mk_pp (tval, m) - << " val: " << mk_pp (val, m) << "\n";); + TRACE("mbqi_project_verbose", + tout << "term: " << mk_pp(term, m) + << " tval: " << tval << " val: " << val << "\n";); // -- if the term does not contain an occurrence of var // -- and is in the same equivalence class in the model - if (tval == val && !occurs (var, term)) { - TRACE ("mbqi_project", - tout << "MBQI: replacing " << mk_pp (var, m) << " with " << mk_pp (term, m) << "\n";); + if (tval == val && !occurs(var, term)) { + TRACE("mbqi_project", + tout << "MBQI: replacing " << mk_pp(var, m) + << " with " << mk_pp(term, m) << "\n";); expr_safe_replace sub(m); - sub.insert (var, term); - sub (fml); + sub.insert(var, term); + sub(fml); return true; } } - TRACE ("mbqi_project", - tout << "MBQI: failed to eliminate " << mk_pp (var, m) << " from " << mk_pp (fml, m) << "\n";); + TRACE("mbqi_project", + tout << "MBQI: failed to eliminate " << mk_pp(var, m) + << " from " << fml << "\n";); return false; } - void mbqi_project (model &M, app_ref_vector &vars, expr_ref &fml) - { - ast_manager &m = fml.get_manager (); - model_evaluator_util mev(m); - mev.set_model (M); + void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml) { + ast_manager &m = fml.get_manager(); expr_ref tmp(m); + model::scoped_model_completion _sc_(mdl, false); // -- evaluate to initialize mev cache - mev.eval (fml, tmp, false); - tmp.reset (); + tmp = mdl(fml); + tmp.reset(); - for (unsigned idx = 0; idx < vars.size (); ) { - if (mbqi_project_var (mev, vars.get (idx), fml)) { - vars[idx] = vars.back (); - vars.pop_back (); - } else { - idx++; - } + unsigned j = 0; + for (app* v : vars) + if (!mbqi_project_var(mdl, v, fml)) + vars[j++] = v; + vars.shrink(j); + } + + struct found {}; + struct check_select { + array_util a; + check_select(ast_manager& m): a(m) {} + void operator()(expr* n) {} + void operator()(app* n) { if (a.is_select(n)) throw found(); } + }; + + bool contains_selects(expr* fml, ast_manager& m) { + check_select cs(m); + try { + for_each_expr(cs, fml); + return false; + } + catch(found) { + return true; } } -bool contains_selects(expr* fml, ast_manager& m) -{ - array_util a_util(m); - if (!is_app(fml)) { return false; } - ast_mark done; - ptr_vector todo; - todo.push_back (to_app (fml)); - while (!todo.empty ()) { - app* a = todo.back (); - if (done.is_marked (a)) { - todo.pop_back (); - continue; - } - unsigned num_args = a->get_num_args (); - bool all_done = true; - for (unsigned i = 0; i < num_args; i++) { - expr* arg = a->get_arg (i); - if (!done.is_marked (arg) && is_app (arg)) { - todo.push_back (to_app (arg)); - all_done = false; - } - } - if (!all_done) { continue; } - todo.pop_back (); - if (a_util.is_select (a)) - { return true; } - done.mark (a, true); + struct collect_indices { + app_ref_vector& m_indices; + array_util a; + collect_indices(app_ref_vector& indices): m_indices(indices), a(indices.get_manager()) {} + void operator()(expr* n) {} + void operator()(app* n) { + if (a.is_select(n)) + for (unsigned i = 1; i < n->get_num_args(); ++i) + if (is_app(n->get_arg(i))) + m_indices.push_back(to_app(n->get_arg(i))); } - return false; + }; + + void get_select_indices(expr* fml, app_ref_vector &indices, ast_manager& m) { + collect_indices ci(indices); + for_each_expr(ci, fml); } -void get_select_indices(expr* fml, app_ref_vector& indices, ast_manager& m) -{ - array_util a_util(m); - if (!is_app(fml)) { return; } - ast_mark done; - ptr_vector todo; - todo.push_back (to_app (fml)); - while (!todo.empty ()) { - app* a = todo.back (); - if (done.is_marked (a)) { - todo.pop_back (); - continue; - } - unsigned num_args = a->get_num_args (); - bool all_done = true; - for (unsigned i = 0; i < num_args; i++) { - expr* arg = a->get_arg (i); - if (!done.is_marked (arg) && is_app (arg)) { - todo.push_back (to_app (arg)); - all_done = false; - } - } - if (!all_done) { continue; } - todo.pop_back (); - if (a_util.is_select (a)) { - SASSERT(a->get_num_args() == 2); - indices.push_back(to_app(a->get_arg(1))); - } - done.mark (a, true); + struct collect_decls { + app_ref_vector& m_decls; + std::string& prefix; + collect_decls(app_ref_vector& decls, std::string& p): m_decls(decls), prefix(p) {} + void operator()(expr* n) {} + void operator()(app* n) { + if (n->get_decl()->get_name().str().find(prefix) != std::string::npos) + m_decls.push_back(n); } + }; + + void find_decls(expr* fml, app_ref_vector& decls, std::string& prefix) { + collect_decls cd(decls, prefix); + for_each_expr(cd, fml); } -void find_decls(expr* fml, app_ref_vector& decls, std::string& prefix) -{ - if (!is_app(fml)) { return; } - ast_mark done; - ptr_vector todo; - todo.push_back (to_app (fml)); - while (!todo.empty ()) { - app* a = todo.back (); - if (done.is_marked (a)) { - todo.pop_back (); - continue; - } - unsigned num_args = a->get_num_args (); - bool all_done = true; - for (unsigned i = 0; i < num_args; i++) { - expr* arg = a->get_arg (i); - if (!done.is_marked (arg) && is_app (arg)) { - todo.push_back (to_app (arg)); - all_done = false; - } - } - if (!all_done) { continue; } - todo.pop_back (); - if (a->get_decl()->get_name().str().find(prefix) != std::string::npos) - { decls.push_back(a); } - done.mark (a, true); - } - return; } -} template class rewriter_tpl; template class rewriter_tpl; -template class rewriter_tpl; diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 7be2ee4b3..9912769c5 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -40,133 +40,102 @@ Revision History: class model; class model_core; -class model_evaluator; namespace spacer { -inline unsigned infty_level () {return UINT_MAX;} - -inline bool is_infty_level(unsigned lvl) -{ return lvl == infty_level (); } - -inline unsigned next_level(unsigned lvl) -{ return is_infty_level(lvl)?lvl:(lvl+1); } - -inline unsigned prev_level (unsigned lvl) -{ - if(is_infty_level(lvl)) { return infty_level(); } - if(lvl == 0) { return 0; } - return lvl -1; -} - -struct pp_level { - unsigned m_level; - pp_level(unsigned l): m_level(l) {} -}; - -inline std::ostream& operator<<(std::ostream& out, pp_level const& p) -{ - if (is_infty_level(p.m_level)) { - return out << "oo"; - } else { - return out << p.m_level; + inline unsigned infty_level () { + return UINT_MAX; } -} + + inline bool is_infty_level(unsigned lvl) { + return lvl == infty_level (); + } + + inline unsigned next_level(unsigned lvl) { + return is_infty_level(lvl)?lvl:(lvl+1); + } + + inline unsigned prev_level (unsigned lvl) { + if (is_infty_level(lvl)) return infty_level(); + if (lvl == 0) return 0; + return lvl - 1; + } + + struct pp_level { + unsigned m_level; + pp_level(unsigned l): m_level(l) {} + }; + + inline std::ostream& operator<<(std::ostream& out, pp_level const& p) { + if (is_infty_level(p.m_level)) { + return out << "oo"; + } else { + return out << p.m_level; + } + } + + typedef ptr_vector app_vector; + typedef ptr_vector decl_vector; + typedef obj_hashtable func_decl_set; + + /** + \brief hoist non-boolean if expressions. + */ + + void to_mbp_benchmark(std::ostream &out, const expr* fml, const app_ref_vector &vars); + // TBD: deprecate by qe::mbp + /** + * do the following in sequence + * 1. use qe_lite to cheaply eliminate vars + * 2. for remaining boolean vars, substitute using M + * 3. use MBP for remaining array and arith variables + * 4. for any remaining arith variables, substitute using M + */ + void qe_project (ast_manager& m, app_ref_vector& vars, + expr_ref& fml, model &mdl, + bool reduce_all_selects=false, + bool native_mbp=false, + bool dont_sub=false); + // deprecate + void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, + model_ref& M, expr_map& map); -typedef ptr_vector app_vector; -typedef ptr_vector decl_vector; -typedef obj_hashtable func_decl_set; + // TBD: sort out + void expand_literals(ast_manager &m, expr_ref_vector& conjs); + void compute_implicant_literals(model &mdl, + expr_ref_vector &formula, + expr_ref_vector &res); + void simplify_bounds (expr_ref_vector &lemmas); + void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false); + /** + * Ground expression by replacing all free variables by skolem + * constants. On return, out is the resulting expression, and vars is + * a map from variable ids to corresponding skolem constants. + */ + void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars); -class model_evaluator_util { - ast_manager& m; - model_ref m_model; - model_evaluator* m_mev; + void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml); - /// initialize with a given model. All previous state is lost. model can be NULL - void reset (model *model); -public: - model_evaluator_util(ast_manager& m); - ~model_evaluator_util(); + bool contains_selects (expr* fml, ast_manager& m); + void get_select_indices (expr* fml, app_ref_vector& indices, ast_manager& m); - void set_model(model &model) {reset (&model);} - model_ref &get_model() {return m_model;} - ast_manager& get_ast_manager() const {return m;} + void find_decls (expr* fml, app_ref_vector& decls, std::string& prefix); -public: - bool is_true (const expr_ref_vector &v); - bool is_false(expr* x); - bool is_true(expr* x); - - bool eval (const expr_ref_vector &v, expr_ref &result, bool model_completion); - /// evaluates an expression - bool eval (expr *e, expr_ref &result, bool model_completion); - // expr_ref eval(expr* e, bool complete=true); -}; - - -/** - \brief replace variables that are used in many disequalities by - an equality using the model. - - Assumption: the model satisfies the conjunctions. -*/ -void reduce_disequalities(model& model, unsigned threshold, expr_ref& fml); - -/** - \brief hoist non-boolean if expressions. -*/ -void hoist_non_bool_if(expr_ref& fml); - -bool is_difference_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls); - -bool is_utvpi_logic(ast_manager& m, unsigned num_fmls, expr* const* fmls); - -/** - * do the following in sequence - * 1. use qe_lite to cheaply eliminate vars - * 2. for remaining boolean vars, substitute using M - * 3. use MBP for remaining array and arith variables - * 4. for any remaining arith variables, substitute using M - */ -void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, - const model_ref& M, bool reduce_all_selects=false, bool native_mbp=false, - bool dont_sub=false); - -void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, model_ref& M, expr_map& map); - -void expand_literals(ast_manager &m, expr_ref_vector& conjs); -void compute_implicant_literals (model_evaluator_util &mev, - expr_ref_vector &formula, expr_ref_vector &res); -void simplify_bounds (expr_ref_vector &lemmas); -void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false); - -/** ground expression by replacing all free variables by skolem constants */ -void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars); - - -void mbqi_project (model &M, app_ref_vector &vars, expr_ref &fml); - -bool contains_selects (expr* fml, ast_manager& m); -void get_select_indices (expr* fml, app_ref_vector& indices, ast_manager& m); - -void find_decls (expr* fml, app_ref_vector& decls, std::string& prefix); - -/** extended pretty-printer - * used for debugging - * disables aliasing of common sub-expressions -*/ -struct mk_epp : public mk_pp { - params_ref m_epp_params; - expr_ref m_epp_expr; + /** + * extended pretty-printer + * used for debugging + * disables aliasing of common sub-expressions + */ + struct mk_epp : public mk_pp { + params_ref m_epp_params; + expr_ref m_epp_expr; mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = nullptr); - void rw(expr *e, expr_ref &out); - -}; - + void rw(expr *e, expr_ref &out); + }; } #endif diff --git a/src/muz/spacer/spacer_virtual_solver.cpp b/src/muz/spacer/spacer_virtual_solver.cpp deleted file mode 100644 index 903f82d23..000000000 --- a/src/muz/spacer/spacer_virtual_solver.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/** -Copyright (c) 2017 Arie Gurfinkel - -Module Name: - - spacer_virtual_solver.cpp - -Abstract: - - multi-solver view of a single smt::kernel - -Author: - - Arie Gurfinkel - -Notes: - ---*/ - -#include "muz/spacer/spacer_virtual_solver.h" -#include "ast/ast_util.h" -#include "ast/ast_pp_util.h" -#include "muz/spacer/spacer_util.h" -#include "ast/rewriter/bool_rewriter.h" - -#include "ast/proofs/proof_checker.h" -#include "ast/proofs/proof_utils.h" - -#include "ast/scoped_proof.h" - -namespace spacer { -virtual_solver::virtual_solver(virtual_solver_factory &factory, - smt::kernel &context, app* pred) : - solver_na2as(context.m()), - m_factory(factory), - m(context.m()), - m_context(context), - m_pred(pred, m), - m_virtual(!m.is_true(pred)), - m_assertions(m), - m_head(0), - m_flat(m), - m_pushed(false), - m_in_delay_scope(false), - m_dump_benchmarks(factory.fparams().m_dump_benchmarks), - m_dump_counter(0), - m_proof(m) -{ - // -- insert m_pred->true background assumption this will not - // -- change m_context, but will add m_pred to - // -- the private field solver_na2as::m_assumptions - if (m_virtual) - { solver_na2as::assert_expr_core2(m.mk_true(), m_pred); } -} - -virtual_solver::~virtual_solver() -{ - SASSERT(!m_pushed || get_scope_level() > 0); - if (m_pushed) { pop(get_scope_level()); } - - if (m_virtual) { - m_pred = m.mk_not(m_pred); - m_context.assert_expr(m_pred); - } -} - -namespace { - - -// TBD: move to ast/proofs/elim_aux_assertions - - -} - -proof *virtual_solver::get_proof() -{ - scoped_watch _t_(m_factory.m_proof_watch); - - if (!m_proof.get()) { - elim_aux_assertions pc(m_pred); - m_proof = m_context.get_proof(); - pc(m, m_proof.get(), m_proof); - } - return m_proof.get(); -} - -bool virtual_solver::is_aux_predicate(expr *p) -{return is_app(p) && to_app(p) == m_pred.get();} - -lbool virtual_solver::check_sat_core(unsigned num_assumptions, - expr *const * assumptions) -{ - SASSERT(!m_pushed || get_scope_level() > 0); - m_proof.reset(); - scoped_watch _t_(m_factory.m_check_watch); - m_factory.m_stats.m_num_smt_checks++; - - stopwatch sw; - sw.start(); - internalize_assertions(); - if (false) { - std::stringstream file_name; - file_name << "virt_solver"; - if (m_virtual) { file_name << "_" << m_pred->get_decl()->get_name(); } - file_name << "_" << (m_dump_counter++) << ".smt2"; - - verbose_stream() << "Dumping SMT2 benchmark: " << file_name.str() << "\n"; - - std::ofstream out(file_name.str().c_str()); - - to_smt2_benchmark(out, m_context, num_assumptions, assumptions, - "virt_solver"); - - out << "(exit)\n"; - out.close(); - } - lbool res = m_context.check(num_assumptions, assumptions); - sw.stop(); - if (res == l_true) { - m_factory.m_check_sat_watch.add(sw); - m_factory.m_stats.m_num_sat_smt_checks++; - } else if (res == l_undef) { - m_factory.m_check_undef_watch.add(sw); - m_factory.m_stats.m_num_undef_smt_checks++; - } - set_status(res); - - if (m_dump_benchmarks && - sw.get_seconds() >= m_factory.fparams().m_dump_min_time) { - std::stringstream file_name; - file_name << "virt_solver"; - if (m_virtual) { file_name << "_" << m_pred->get_decl()->get_name(); } - file_name << "_" << (m_dump_counter++) << ".smt2"; - - std::ofstream out(file_name.str().c_str()); - - - out << "(set-info :status "; - if (res == l_true) { out << "sat"; } - else if (res == l_false) { out << "unsat"; } - else { out << "unknown"; } - out << ")\n"; - - to_smt2_benchmark(out, m_context, num_assumptions, assumptions, - "virt_solver"); - - out << "(exit)\n"; - ::statistics st; - m_context.collect_statistics(st); - st.update("time", sw.get_seconds()); - st.display_smt2(out); - - out.close(); - - if (m_factory.fparams().m_dump_recheck) { - scoped_no_proof _no_proof_(m); - smt_params p; - stopwatch sw2; - smt::kernel kernel(m, p); - for (unsigned i = 0, sz = m_context.size(); i < sz; ++i) - { kernel.assert_expr(m_context.get_formula(i)); } - sw2.start(); - kernel.check(num_assumptions, assumptions); - sw2.stop(); - verbose_stream() << file_name.str() << " :orig " - << sw.get_seconds() << " :new " << sw2.get_seconds(); - } - } - - - return res; -} - -void virtual_solver::push_core() -{ - SASSERT(!m_pushed || get_scope_level() > 0); - if (m_in_delay_scope) { - // second push - internalize_assertions(); - m_context.push(); - m_pushed = true; - m_in_delay_scope = false; - } - - if (!m_pushed) { m_in_delay_scope = true; } - else { - SASSERT(m_pushed); - SASSERT(!m_in_delay_scope); - m_context.push(); - } -} -void virtual_solver::pop_core(unsigned n) { - SASSERT(!m_pushed || get_scope_level() > 0); - if (m_pushed) { - SASSERT(!m_in_delay_scope); - m_context.pop(n); - m_pushed = get_scope_level() - n > 0; - } - else { - m_in_delay_scope = get_scope_level() - n > 0; - } -} - -void virtual_solver::get_unsat_core(ptr_vector &r) -{ - for (unsigned i = 0, sz = m_context.get_unsat_core_size(); i < sz; ++i) { - expr *core = m_context.get_unsat_core_expr(i); - if (is_aux_predicate(core)) { continue; } - r.push_back(core); - } -} - -void virtual_solver::assert_expr_core(expr *e) -{ - SASSERT(!m_pushed || get_scope_level() > 0); - if (m.is_true(e)) { return; } - if (m_in_delay_scope) { - internalize_assertions(); - m_context.push(); - m_pushed = true; - m_in_delay_scope = false; - } - - if (m_pushed) - { m_context.assert_expr(e); } - else { - m_flat.push_back(e); - flatten_and(m_flat); - m_assertions.append(m_flat); - m_flat.reset(); - } -} -void virtual_solver::internalize_assertions() -{ - SASSERT(!m_pushed || m_head == m_assertions.size()); - for (unsigned sz = m_assertions.size(); m_head < sz; ++m_head) { - expr_ref f(m); - f = m.mk_implies(m_pred, (m_assertions.get(m_head))); - m_context.assert_expr(f); - } -} -void virtual_solver::refresh() -{ - SASSERT(!m_pushed); - m_head = 0; -} - -void virtual_solver::reset() -{ - SASSERT(!m_pushed); - m_head = 0; - m_assertions.reset(); - m_factory.refresh(); -} - -void virtual_solver::get_labels(svector &r) -{ - r.reset(); - buffer tmp; - m_context.get_relevant_labels(nullptr, tmp); - r.append(tmp.size(), tmp.c_ptr()); -} - -solver* virtual_solver::translate(ast_manager& m, params_ref const& p) -{ - UNREACHABLE(); - return nullptr; -} -void virtual_solver::updt_params(params_ref const &p) { m_factory.updt_params(p); } -void virtual_solver::collect_param_descrs(param_descrs &r) { m_factory.collect_param_descrs(r); } -void virtual_solver::set_produce_models(bool f) { m_factory.set_produce_models(f); } -smt_params &virtual_solver::fparams() {return m_factory.fparams();} - -void virtual_solver::to_smt2_benchmark(std::ostream &out, - smt::kernel &context, - unsigned num_assumptions, - expr * const * assumptions, - char const * name, - symbol const &logic, - char const * status, - char const * attributes) -{ - ast_pp_util pp(m); - expr_ref_vector asserts(m); - - - for (unsigned i = 0, sz = context.size(); i < sz; ++i) { - asserts.push_back(context.get_formula(i)); - pp.collect(asserts.back()); - } - pp.collect(num_assumptions, assumptions); - pp.display_decls(out); - pp.display_asserts(out, asserts); - out << "(check-sat "; - for (unsigned i = 0; i < num_assumptions; ++i) - { out << mk_pp(assumptions[i], m) << " "; } - out << ")\n"; -} - - -virtual_solver_factory::virtual_solver_factory(ast_manager &mgr, smt_params &fparams) : - m_fparams(fparams), m(mgr), m_context(m, m_fparams) -{ - m_stats.reset(); -} - -virtual_solver* virtual_solver_factory::mk_solver() -{ - std::stringstream name; - name << "vsolver#" << m_solvers.size(); - app_ref pred(m); - pred = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); - SASSERT(m_context.get_scope_level() == 0); - m_solvers.push_back(alloc(virtual_solver, *this, m_context, pred)); - return m_solvers.back(); -} - -void virtual_solver_factory::collect_statistics(statistics &st) const -{ - m_context.collect_statistics(st); - st.update("time.virtual_solver.smt.total", m_check_watch.get_seconds()); - st.update("time.virtual_solver.smt.total.sat", m_check_sat_watch.get_seconds()); - st.update("time.virtual_solver.smt.total.undef", m_check_undef_watch.get_seconds()); - st.update("time.virtual_solver.proof", m_proof_watch.get_seconds()); - st.update("virtual_solver.checks", m_stats.m_num_smt_checks); - st.update("virtual_solver.checks.sat", m_stats.m_num_sat_smt_checks); - st.update("virtual_solver.checks.undef", m_stats.m_num_undef_smt_checks); -} -void virtual_solver_factory::reset_statistics() -{ - m_context.reset_statistics(); - m_stats.reset(); - m_check_sat_watch.reset(); - m_check_undef_watch.reset(); - m_check_watch.reset(); - m_proof_watch.reset(); -} - -void virtual_solver_factory::refresh() -{ - m_context.reset(); - for (unsigned i = 0, e = m_solvers.size(); i < e; ++i) - { m_solvers [i]->refresh(); } -} - -virtual_solver_factory::~virtual_solver_factory() -{ - for (unsigned i = 0, e = m_solvers.size(); i < e; ++i) - { dealloc(m_solvers [i]); } -} - - - -} diff --git a/src/muz/spacer/spacer_virtual_solver.h b/src/muz/spacer/spacer_virtual_solver.h deleted file mode 100644 index 9dc20c241..000000000 --- a/src/muz/spacer/spacer_virtual_solver.h +++ /dev/null @@ -1,154 +0,0 @@ -/** -Copyright (c) 2017 Arie Gurfinkel - -Module Name: - - spacer_virtual_solver.h - -Abstract: - - multi-solver view of a single smt::kernel - -Author: - - Arie Gurfinkel - -Notes: - ---*/ -#ifndef SPACER_VIRTUAL_SOLVER_H_ -#define SPACER_VIRTUAL_SOLVER_H_ -#include"ast/ast.h" -#include"util/params.h" -#include"solver/solver_na2as.h" -#include"smt/smt_kernel.h" -#include"smt/params/smt_params.h" -#include"util/stopwatch.h" -namespace spacer { -class virtual_solver_factory; - -class virtual_solver : public solver_na2as { - friend class virtual_solver_factory; - -private: - virtual_solver_factory &m_factory; - ast_manager &m; - smt::kernel &m_context; - app_ref m_pred; - - bool m_virtual; - expr_ref_vector m_assertions; - unsigned m_head; - // temporary to flatten conjunction - expr_ref_vector m_flat; - - bool m_pushed; - bool m_in_delay_scope; - bool m_dump_benchmarks; - unsigned m_dump_counter; - - proof_ref m_proof; - - virtual_solver(virtual_solver_factory &factory, smt::kernel &context, app* pred); - - bool is_aux_predicate(expr *p); - void internalize_assertions(); - void to_smt2_benchmark(std::ostream &out, - smt::kernel &context, - unsigned num_assumptions, - expr * const * assumptions, - char const * name = "benchmarks", - symbol const &logic = symbol::null, - char const * status = "unknown", - char const * attributes = ""); - - void refresh(); - -public: - ~virtual_solver() override; - unsigned get_num_assumptions() const override - { - unsigned sz = solver_na2as::get_num_assumptions(); - return m_virtual ? sz - 1 : sz; - } - expr* get_assumption(unsigned idx) const override - { - if(m_virtual) { idx++; } - return solver_na2as::get_assumption(idx); - } - - - void get_unsat_core(ptr_vector &r) override; - void assert_expr_core(expr *e) override; - void collect_statistics(statistics &st) const override {} - void get_model_core(model_ref &m) override {m_context.get_model(m);} - proof* get_proof() override; - std::string reason_unknown() const override - {return m_context.last_failure_as_string();} - void set_reason_unknown(char const *msg) override - {m_context.set_reason_unknown(msg);} - ast_manager& get_manager() const override {return m;} - void get_labels(svector &r) override; - void set_produce_models(bool f) override; - smt_params &fparams(); - void reset(); - expr_ref_vector cube(expr_ref_vector&, unsigned) override { return expr_ref_vector(m); } - void set_progress_callback(progress_callback *callback) override {UNREACHABLE();} - - solver *translate(ast_manager &m, params_ref const &p) override; - - void updt_params(params_ref const &p) override; - void collect_param_descrs(param_descrs &r) override; - - -protected: - lbool check_sat_core(unsigned num_assumptions, expr *const * assumptions) override; - void push_core() override; - void pop_core(unsigned n) override; -}; - -/// multi-solver abstraction on top of a single smt::kernel -class virtual_solver_factory { - friend class virtual_solver; -private: - smt_params &m_fparams; - ast_manager &m; - smt::kernel m_context; - /// solvers managed by this factory - ptr_vector m_solvers; - - struct stats { - unsigned m_num_smt_checks; - unsigned m_num_sat_smt_checks; - unsigned m_num_undef_smt_checks; - stats() { reset(); } - void reset() { memset(this, 0, sizeof(*this)); } - }; - - stats m_stats; - stopwatch m_check_watch; - stopwatch m_check_sat_watch; - stopwatch m_check_undef_watch; - stopwatch m_proof_watch; - - - void refresh(); - - smt_params &fparams() { return m_fparams; } - -public: - virtual_solver_factory(ast_manager &mgr, smt_params &fparams); - virtual ~virtual_solver_factory(); - virtual_solver* mk_solver(); - void collect_statistics(statistics &st) const; - void reset_statistics(); - void updt_params(params_ref const &p) { m_fparams.updt_params(p); } - void collect_param_descrs(param_descrs &r) { /* empty */ } - void set_produce_models(bool f) { m_fparams.m_model = f; } - bool get_produce_models() { return m_fparams.m_model; } -}; - -} - - -#endif diff --git a/src/muz/tab/tab_context.cpp b/src/muz/tab/tab_context.cpp index 39b7af634..7200b3522 100644 --- a/src/muz/tab/tab_context.cpp +++ b/src/muz/tab/tab_context.cpp @@ -30,7 +30,7 @@ Revision History: #include "ast/for_each_expr.h" #include "ast/substitution/matcher.h" #include "ast/scoped_proof.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include "ast/ast_util.h" namespace tb { @@ -693,13 +693,12 @@ namespace tb { m_solver.assert_expr(postcond); lbool is_sat = m_solver.check(); if (is_sat == l_true) { - expr_ref tmp(m); expr* n; model_ref mdl; m_solver.get_model(mdl); for (unsigned i = 0; i < fmls.size(); ++i) { n = fmls[i].get(); - if (mdl->eval(n, tmp) && m.is_false(tmp)) { + if (mdl->is_false(n)) { m_refs.push_back(normalize(n)); m_sat_lits.insert(m_refs.back()); } diff --git a/src/muz/transforms/dl_mk_array_blast.cpp b/src/muz/transforms/dl_mk_array_blast.cpp index 6894cf4fa..34e739bc3 100644 --- a/src/muz/transforms/dl_mk_array_blast.cpp +++ b/src/muz/transforms/dl_mk_array_blast.cpp @@ -42,7 +42,7 @@ namespace datalog { } bool mk_array_blast::is_store_def(expr* e, expr*& x, expr*& y) { - if (m.is_iff(e, x, y) || m.is_eq(e, x, y)) { + if (m.is_eq(e, x, y)) { if (!a.is_store(y)) { std::swap(x,y); } diff --git a/src/muz/transforms/dl_mk_array_eq_rewrite.cpp b/src/muz/transforms/dl_mk_array_eq_rewrite.cpp index c33cecda9..c8a3d4357 100644 --- a/src/muz/transforms/dl_mk_array_eq_rewrite.cpp +++ b/src/muz/transforms/dl_mk_array_eq_rewrite.cpp @@ -20,9 +20,9 @@ Revision History: #include "ast/expr_abstract.h" #include "muz/base/dl_context.h" #include "muz/base/dl_context.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include "muz/transforms/dl_mk_array_eq_rewrite.h" -#include "ast/factor_equivs.h" +#include "ast/rewriter/factor_equivs.h" namespace datalog { diff --git a/src/muz/transforms/dl_mk_array_instantiation.cpp b/src/muz/transforms/dl_mk_array_instantiation.cpp index 362d83865..6a8f0ce81 100644 --- a/src/muz/transforms/dl_mk_array_instantiation.cpp +++ b/src/muz/transforms/dl_mk_array_instantiation.cpp @@ -23,7 +23,7 @@ Revision History: #include "muz/base/dl_context.h" #include "ast/rewriter/expr_safe_replace.h" #include "ast/expr_abstract.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { diff --git a/src/muz/transforms/dl_mk_array_instantiation.h b/src/muz/transforms/dl_mk_array_instantiation.h index b2e80ab84..51a818a3f 100644 --- a/src/muz/transforms/dl_mk_array_instantiation.h +++ b/src/muz/transforms/dl_mk_array_instantiation.h @@ -70,7 +70,7 @@ Revision History: #define DL_MK_ARRAY_INSTANTIATION_H_ -#include "ast/factor_equivs.h" +#include "ast/rewriter/factor_equivs.h" #include "muz/base/dl_rule_transformer.h" namespace datalog { diff --git a/src/muz/transforms/dl_mk_bit_blast.cpp b/src/muz/transforms/dl_mk_bit_blast.cpp index 8a21bc37c..992b8f51b 100644 --- a/src/muz/transforms/dl_mk_bit_blast.cpp +++ b/src/muz/transforms/dl_mk_bit_blast.cpp @@ -24,7 +24,7 @@ Revision History: #include "ast/rewriter/expr_safe_replace.h" #include "tactic/generic_model_converter.h" #include "muz/transforms/dl_mk_interp_tail_simplifier.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include "ast/scoped_proof.h" #include "model/model_v2_pp.h" @@ -32,9 +32,9 @@ namespace datalog { // // P(v) :- Q(extract[1:1]v ++ 0), R(1 ++ extract[0:0]v). - // -> + // -> // P(bv(x,y)) :- Q(bv(x,0)), R(bv(1,y)) . - // + // // Introduce P_bv: // P_bv(x,y) :- Q_bv(x,0), R_bv(1,y) // P(bv(x,y)) :- P_bv(x,y) @@ -51,7 +51,7 @@ namespace datalog { bit_blast_model_converter(ast_manager& m): m(m), m_bv(m), - m_old_funcs(m), + m_old_funcs(m), m_new_funcs(m) {} void insert(func_decl* old_f, func_decl* new_f) { @@ -73,7 +73,7 @@ namespace datalog { func_decl* q = m_old_funcs[i].get(); func_interp* f = model->get_func_interp(p); if (!f) continue; - expr_ref body(m); + expr_ref body(m); unsigned arity_q = q->get_arity(); TRACE("dl", model_v2_pp(tout, *model); @@ -87,10 +87,10 @@ namespace datalog { if (f) { body = f->get_interp(); SASSERT(!f->is_partial()); - SASSERT(body); + SASSERT(body); } else { - body = m.mk_false(); + body = m.mk_false(); } unsigned idx = 0; expr_ref arg(m), proj(m); @@ -104,18 +104,18 @@ namespace datalog { for (unsigned k = 0; k < sz; ++k) { parameter p(k); proj = m.mk_app(m_bv.get_family_id(), OP_BIT2BOOL, 1, &p, 1, &t); - sub.insert(m.mk_var(idx++, m.mk_bool_sort()), proj); + sub.insert(m.mk_var(idx++, m.mk_bool_sort()), proj); } } else { sub.insert(m.mk_var(idx++, s), arg); } } - sub(body); + sub(body); g->set_else(body); model->register_decl(q, g); - } - } + } + } }; class expand_mkbv_cfg : public default_rewriter_cfg { @@ -134,10 +134,10 @@ namespace datalog { public: expand_mkbv_cfg(context& ctx): - m_context(ctx), + m_context(ctx), m(ctx.get_manager()), m_util(m), - m_args(m), + m_args(m), m_f_vars(m), m_g_vars(m), m_old_funcs(m), @@ -152,8 +152,8 @@ namespace datalog { void set_dst(rule_set* dst) { m_dst = dst; } func_decl_ref_vector const& old_funcs() const { return m_old_funcs; } func_decl_ref_vector const& new_funcs() const { return m_new_funcs; } - - br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { if (num == 0) { if (m_src->is_output_predicate(f)) m_dst->set_output_predicate(f); @@ -165,9 +165,9 @@ namespace datalog { return BR_FAILED; } - // + // // f(mk_bv(args),...) - // + // m_args.reset(); m_g_vars.reset(); m_f_vars.reset(); @@ -191,9 +191,9 @@ namespace datalog { } } func_decl* g = nullptr; - + if (!m_pred2blast.find(f, g)) { - + ptr_vector domain; for (unsigned i = 0; i < m_args.size(); ++i) { domain.push_back(m.get_sort(m_args[i].get())); @@ -262,7 +262,7 @@ namespace datalog { m_params.set_bool("blast_quant", true); m_blaster.updt_params(m_params); } - + rule_set * operator()(rule_set const & source) { // TODO pc if (!m_context.xform_bit_blast()) { @@ -270,8 +270,8 @@ namespace datalog { } rule_manager& rm = m_context.get_rule_manager(); unsigned sz = source.get_num_rules(); - expr_ref fml(m); - rule_set * result = alloc(rule_set, m_context); + expr_ref fml(m); + rule_set * result = alloc(rule_set, m_context); m_rewriter.m_cfg.set_src(&source); m_rewriter.m_cfg.set_dst(result); for (unsigned i = 0; !m_context.canceled() && i < sz; ++i) { @@ -299,8 +299,8 @@ namespace datalog { if (!source.contains(*I)) result->set_output_predicate(*I); } - - if (m_context.get_model_converter()) { + + if (m_context.get_model_converter()) { generic_model_converter* fmc = alloc(generic_model_converter, m, "dl_mk_bit_blast"); bit_blast_model_converter* bvmc = alloc(bit_blast_model_converter, m); func_decl_ref_vector const& old_funcs = m_rewriter.m_cfg.old_funcs(); @@ -311,7 +311,7 @@ namespace datalog { } m_context.add_model_converter(concat(bvmc, fmc)); } - + return result; } }; @@ -326,6 +326,6 @@ namespace datalog { rule_set * mk_bit_blast::operator()(rule_set const & source) { return (*m_impl)(source); - } + } }; diff --git a/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp b/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp index 38000c65a..c1abd7bef 100644 --- a/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp +++ b/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp @@ -27,7 +27,7 @@ Revision History: #include "muz/transforms/dl_mk_interp_tail_simplifier.h" #include "ast/ast_util.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { // ----------------------------------- @@ -46,7 +46,7 @@ namespace datalog { bool mk_interp_tail_simplifier::rule_substitution::unify(expr * e1, expr * e2) { SASSERT(m_rule); - //we need to apply the current substitution in order to ensure the unifier + //we need to apply the current substitution in order to ensure the unifier //works in an incremental way expr_ref e1_s(m); expr_ref e2_s(m); @@ -268,7 +268,7 @@ namespace datalog { if (neq) { have_pair = false; v[prev_pair_idx] = neq; - + read_idx++; continue; } @@ -294,7 +294,7 @@ namespace datalog { //bool detect_same_variable_conj_pairs - br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { if (m.is_not(f) && (m.is_and(args[0]) || m.is_or(args[0]))) { @@ -307,15 +307,15 @@ namespace datalog { m_app_args.push_back(tmp); } if (m.is_and(args[0])) { - result = mk_or(m_app_args); + result = mk_or(m_app_args); } else { - result = mk_and(m_app_args); + result = mk_and(m_app_args); } return BR_REWRITE2; } - if (!m.is_and(f) && !m.is_or(f)) { - return BR_FAILED; + if (!m.is_and(f) && !m.is_or(f)) { + return BR_FAILED; } if (num == 0) { if (m.is_and(f)) { @@ -375,7 +375,7 @@ namespace datalog { m_simp(ctx.get_rewriter()), a(m), m_rule_subst(ctx), - m_tail(m), + m_tail(m), m_itail_members(m), m_conj(m) { m_cfg = alloc(normalizer_cfg, m); @@ -386,7 +386,7 @@ namespace datalog { dealloc(m_rw); dealloc(m_cfg); } - + void mk_interp_tail_simplifier::simplify_expr(app * a, expr_ref& res) { @@ -537,7 +537,7 @@ namespace datalog { simplify_expr(itail.get(), simp_res); modified |= itail.get() != simp_res.get(); - + if (m.is_false(simp_res)) { TRACE("dl", r->display(m_context, tout << "rule is infeasible\n");); return false; @@ -568,7 +568,7 @@ namespace datalog { rule_ref pro_var_eq_result(m_context.get_rule_manager()); if (propagate_variable_equivalences(res, pro_var_eq_result)) { - SASSERT(rule_counter().get_max_rule_var(*r.get())==0 || + SASSERT(rule_counter().get_max_rule_var(*r.get())==0 || rule_counter().get_max_rule_var(*r.get()) > rule_counter().get_max_rule_var(*pro_var_eq_result.get())); r = pro_var_eq_result; goto start; @@ -607,8 +607,8 @@ namespace datalog { rule_set * res = alloc(rule_set, m_context); if (transform_rules(source, *res)) { res->inherit_predicates(source); - TRACE("dl", - source.display(tout); + TRACE("dl", + source.display(tout); res->display(tout);); } else { dealloc(res); @@ -616,6 +616,5 @@ namespace datalog { } return res; } - -}; +}; diff --git a/src/muz/transforms/dl_mk_quantifier_abstraction.cpp b/src/muz/transforms/dl_mk_quantifier_abstraction.cpp index e9a4f2f54..dafb0ddd5 100644 --- a/src/muz/transforms/dl_mk_quantifier_abstraction.cpp +++ b/src/muz/transforms/dl_mk_quantifier_abstraction.cpp @@ -11,7 +11,7 @@ Abstract: Author: - Ken McMillan + Ken McMillan Andrey Rybalchenko Nikolaj Bjorner (nbjorner) 2013-04-02 @@ -23,13 +23,12 @@ Revision History: #include "muz/base/dl_context.h" #include "ast/rewriter/expr_safe_replace.h" #include "ast/expr_abstract.h" -#include "muz/base/fixedpoint_params.hpp" namespace datalog { - // model converter: + // model converter: // Given model for P^(x, y, i, a[i]) // create model: P(x,y,a) == forall i . P^(x,y,i,a[i]) // requires substitution and list of bound variables. @@ -55,7 +54,7 @@ namespace datalog { void display(std::ostream& out) override { display_add(out, m); } - void get_units(obj_map& units) override { units.reset(); } + void get_units(obj_map& units) override { units.reset(); } void insert(func_decl* old_p, func_decl* new_p, expr_ref_vector& sub, sort_ref_vector& sorts, svector const& bound) { m_old_funcs.push_back(old_p); @@ -74,7 +73,7 @@ namespace datalog { sort_ref_vector const& sorts = m_sorts[i]; svector const& is_bound = m_bound[i]; func_interp* f = old_model->get_func_interp(p); - expr_ref body(m); + expr_ref body(m); unsigned arity_q = q->get_arity(); SASSERT(0 < p->get_arity()); func_interp* g = alloc(func_interp, m, arity_q); @@ -82,7 +81,7 @@ namespace datalog { if (f) { body = f->get_interp(); SASSERT(!f->is_partial()); - SASSERT(body); + SASSERT(body); } else { expr_ref_vector args(m); @@ -94,7 +93,7 @@ namespace datalog { // Create quantifier wrapper around body. TRACE("dl", tout << mk_pp(body, m) << "\n";); - // 1. replace variables by the compound terms from + // 1. replace variables by the compound terms from // the original predicate. expr_safe_replace rep(m); for (unsigned i = 0; i < sub.size(); ++i) { @@ -121,7 +120,7 @@ namespace datalog { _free.push_back(consts.back()); } } - rep(body); + rep(body); rep.reset(); TRACE("dl", tout << mk_pp(body, m) << "\n";); @@ -130,18 +129,18 @@ namespace datalog { body = m.mk_forall(names.size(), bound_sorts.c_ptr(), names.c_ptr(), body); TRACE("dl", tout << mk_pp(body, m) << "\n";); - // 4. replace remaining constants by variables. + // 4. replace remaining constants by variables. for (unsigned i = 0; i < _free.size(); ++i) { rep.insert(_free[i].get(), m.mk_var(i, m.get_sort(_free[i].get()))); } - rep(body); + rep(body); g->set_else(body); TRACE("dl", tout << mk_pp(body, m) << "\n";); new_model->register_decl(q, g); - } + } old_model = new_model; - } + } }; mk_quantifier_abstraction::mk_quantifier_abstraction( @@ -154,7 +153,7 @@ namespace datalog { m_mc(nullptr) { } - mk_quantifier_abstraction::~mk_quantifier_abstraction() { + mk_quantifier_abstraction::~mk_quantifier_abstraction() { } func_decl* mk_quantifier_abstraction::declare_pred(rule_set const& rules, rule_set& dst, func_decl* old_p) { @@ -178,7 +177,7 @@ namespace datalog { func_decl* new_p = nullptr; if (!m_old2new.find(old_p, new_p)) { expr_ref_vector sub(m), vars(m); - svector bound; + svector bound; sort_ref_vector domain(m), sorts(m); expr_ref arg(m); for (unsigned i = 0; i < sz; ++i) { @@ -208,7 +207,7 @@ namespace datalog { bound.push_back(false); sub.push_back(arg); sorts.push_back(s0); - } + } SASSERT(old_p->get_range() == m.mk_bool_sort()); new_p = m.mk_func_decl(old_p->get_name(), domain.size(), domain.c_ptr(), old_p->get_range()); m_refs.push_back(new_p); @@ -242,12 +241,12 @@ namespace datalog { } args.push_back(arg); } - TRACE("dl", + TRACE("dl", tout << mk_pp(new_p, m) << "\n"; for (unsigned i = 0; i < args.size(); ++i) { tout << mk_pp(args[i].get(), m) << "\n"; }); - return app_ref(m.mk_app(new_p, args.size(), args.c_ptr()), m); + return app_ref(m.mk_app(new_p, args.size(), args.c_ptr()), m); } app_ref mk_quantifier_abstraction::mk_tail(rule_set const& rules, rule_set& dst, app* p) { @@ -272,7 +271,7 @@ namespace datalog { for (unsigned i = 0; i < sz; ++i) { arg = ps->get_arg(i); sort* s = m.get_sort(arg); - bool is_pattern = false; + bool is_pattern = false; while (a.is_array(s)) { is_pattern = true; unsigned arity = get_array_arity(s); @@ -304,9 +303,9 @@ namespace datalog { ptr_vector args2; args2.push_back(arg); args2.append(num_args, args); - return a.mk_select(args2.size(), args2.c_ptr()); + return a.mk_select(args2.size(), args2.c_ptr()); } - + rule_set * mk_quantifier_abstraction::operator()(rule_set const & source) { if (!m_ctx.quantify_arrays()) { return nullptr; @@ -334,10 +333,10 @@ namespace datalog { } rule_set * result = alloc(rule_set, m_ctx); - for (unsigned i = 0; i < sz; ++i) { + for (unsigned i = 0; i < sz; ++i) { tail.reset(); rule & r = *source.get_rule(i); - TRACE("dl", r.display(m_ctx, tout); ); + TRACE("dl", r.display(m_ctx, tout); ); unsigned cnt = vc.get_max_rule_var(r)+1; unsigned utsz = r.get_uninterpreted_tail_size(); unsigned tsz = r.get_tail_size(); @@ -352,8 +351,8 @@ namespace datalog { proof_ref pr(m); rm.mk_rule(fml, pr, *result, r.name()); TRACE("dl", result->last()->display(m_ctx, tout);); - } - + } + // proof converter: proofs are not necessarily preserved using this transformation. if (m_old2new.empty()) { @@ -371,5 +370,3 @@ namespace datalog { }; - - diff --git a/src/muz/transforms/dl_mk_quantifier_instantiation.cpp b/src/muz/transforms/dl_mk_quantifier_instantiation.cpp index 4cbcc5712..9f6302e05 100644 --- a/src/muz/transforms/dl_mk_quantifier_instantiation.cpp +++ b/src/muz/transforms/dl_mk_quantifier_instantiation.cpp @@ -180,7 +180,7 @@ namespace datalog { } m_terms[n] = e; visited.mark(e); - if (m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) { + if (m.is_eq(e, e1, e2)) { m_uf.merge(e1->get_id(), e2->get_id()); } if (is_app(e)) { diff --git a/src/muz/transforms/dl_mk_rule_inliner.cpp b/src/muz/transforms/dl_mk_rule_inliner.cpp index 584f44303..317f13ff8 100644 --- a/src/muz/transforms/dl_mk_rule_inliner.cpp +++ b/src/muz/transforms/dl_mk_rule_inliner.cpp @@ -18,7 +18,7 @@ Revision History: Added linear_inline 2012-9-10 (nbjorner) Disable inliner for quantified rules 2012-10-31 (nbjorner) - + Notes: Resolution transformation (resolve): @@ -27,7 +27,7 @@ Resolution transformation (resolve): -------------------------------------------------- P(x) :- R(z), phi(x,y), psi(y,z) - Proof converter: + Proof converter: replace assumption (*) by rule and upper assumptions. @@ -37,9 +37,9 @@ Subsumption transformation (remove rule): P(x) :- Q(y), phi(x,y) Rules --------------------------------- Rules - - - Model converter: + + + Model converter: P(x) := P(x) or (exists y . Q(y) & phi(x,y)) @@ -52,7 +52,7 @@ Subsumption transformation (remove rule): #include "ast/rewriter/rewriter.h" #include "ast/rewriter/rewriter_def.h" #include "muz/transforms/dl_mk_rule_inliner.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { @@ -67,15 +67,15 @@ namespace datalog { unsigned var_cnt = std::max(vc.get_max_rule_var(tgt), vc.get_max_rule_var(src))+1; m_subst.reset(); m_subst.reserve(2, var_cnt); - + m_ready = m_unif(tgt.get_tail(tgt_idx), src.get_head(), m_subst); if (m_ready) { m_deltas[0] = 0; m_deltas[1] = var_cnt; - TRACE("dl", - output_predicate(m_context, src.get_head(), tout << "unify rules "); - output_predicate(m_context, tgt.get_head(), tout << "\n"); + TRACE("dl", + output_predicate(m_context, src.get_head(), tout << "unify rules "); + output_predicate(m_context, tgt.get_head(), tout << "\n"); tout << "\n";); } return m_ready; @@ -90,7 +90,7 @@ namespace datalog { } void rule_unifier::apply( - rule const& r, bool is_tgt, unsigned skipped_index, + rule const& r, bool is_tgt, unsigned skipped_index, app_ref_vector& res, svector& res_neg) { unsigned rule_len = r.get_tail_size(); for (unsigned i = 0; i < rule_len; i++) { @@ -127,7 +127,7 @@ namespace datalog { ); if (m_normalize) { - m_rm.fix_unbound_vars(res, true); + m_rm.fix_unbound_vars(res, true); if (m_interp_simplifier.transform_rule(res.get(), simpl_rule)) { res = simpl_rule; return true; @@ -150,8 +150,8 @@ namespace datalog { for (unsigned i = 0; i < sorts.size(); ++i) { v = m.mk_var(i, sorts[i]); m_subst.apply(2, m_deltas, expr_offset(v, is_tgt?0:1), w); - result.push_back(w); - } + result.push_back(w); + } return result; } @@ -184,7 +184,7 @@ namespace datalog { expr_ref_vector s2 = m_unifier.get_rule_subst(src, false); datalog::resolve_rule(m_rm, tgt, src, tail_index, s1, s2, *res.get()); } - return true; + return true; } else { TRACE("dl", res->display(m_context, tout << "interpreted tail is unsat\n");); @@ -240,12 +240,12 @@ namespace datalog { return false; } - // - // these conditions are optional, they avoid possible exponential increase + // + // these conditions are optional, they avoid possible exponential increase // in the size of the problem - // + // - return + return //m_head_pred_non_empty_tails_ctr.get(pred)<=1 m_head_pred_ctr.get(pred) <= 1 || (m_tail_pred_ctr.get(pred) <= 1 && m_head_pred_ctr.get(pred) <= 4) @@ -253,7 +253,7 @@ namespace datalog { } /** Caller has to dealloc the returned object */ - rule_set * mk_rule_inliner::create_allowed_rule_set(rule_set const & orig) + rule_set * mk_rule_inliner::create_allowed_rule_set(rule_set const & orig) { rule_set * res = alloc(rule_set, m_context); for (rule * r : orig) { @@ -268,7 +268,7 @@ namespace datalog { /** Try to make the set of inlined predicates acyclic by forbidding inlining of one - predicate from each strongly connected component. Return true if we did forbide some + predicate from each strongly connected component. Return true if we did forbide some predicate, and false if the set of rules is already acyclic. */ bool mk_rule_inliner::forbid_preds_from_cycles(rule_set const & r) @@ -276,7 +276,7 @@ namespace datalog { SASSERT(r.is_closed()); bool something_forbidden = false; - + const rule_stratifier::comp_vector& comps = r.get_stratifier().get_strats(); for (rule_stratifier::item_set * stratum : comps) { @@ -293,12 +293,12 @@ namespace datalog { return something_forbidden; } - bool mk_rule_inliner::forbid_multiple_multipliers(const rule_set & orig, + bool mk_rule_inliner::forbid_multiple_multipliers(const rule_set & orig, rule_set const & proposed_inlined_rules) { bool something_forbidden = false; - const rule_stratifier::comp_vector& comps = + const rule_stratifier::comp_vector& comps = proposed_inlined_rules.get_stratifier().get_strats(); for (rule_stratifier::item_set * stratum : comps) { @@ -332,7 +332,7 @@ namespace datalog { } else { is_multi_head_pred = true; - m_head_pred_ctr.get(head_pred) = + m_head_pred_ctr.get(head_pred) = m_head_pred_ctr.get(head_pred)*tail_pred_head_cnt; } } @@ -379,7 +379,7 @@ namespace datalog { void mk_rule_inliner::plan_inlining(rule_set const & orig) { count_pred_occurrences(orig); - + scoped_ptr candidate_inlined_set = create_allowed_rule_set(orig); while (forbid_preds_from_cycles(*candidate_inlined_set)) { candidate_inlined_set = create_allowed_rule_set(orig); @@ -458,8 +458,8 @@ namespace datalog { rule_ref r(rl, m_rm); func_decl * pred = r->get_decl(); - // if inlining is allowed, then we are eliminating - // this relation through inlining, + // if inlining is allowed, then we are eliminating + // this relation through inlining, // so we don't add its rules to the result something_done |= !inlining_allowed(orig, pred) && transform_rule(orig, r, tgt); @@ -472,14 +472,14 @@ namespace datalog { } } } - + return something_done; } /** Check whether rule r is oriented in a particular ordering. This is to avoid infinite cycle of inlining in the eager inliner. - + Out ordering is lexicographic, comparing atoms first on stratum they are in, then on arity and then on ast ID of their func_decl. */ @@ -488,7 +488,7 @@ namespace datalog { unsigned head_strat = strat.get_predicate_strat(head_pred); unsigned head_arity = head_pred->get_arity(); unsigned pt_len = r->get_positive_tail_size(); - for (unsigned ti=0; ti < pt_len; ++ti) { + for (unsigned ti=0; ti < pt_len; ++ti) { func_decl * pred = r->get_decl(ti); unsigned pred_strat = strat.get_predicate_strat(pred); SASSERT(pred_strat <= head_strat); @@ -516,7 +516,7 @@ namespace datalog { unsigned pt_len = r->get_positive_tail_size(); for (unsigned ti = 0; ti < pt_len; ++ti) { - + func_decl * pred = r->get_decl(ti); if (pred == head_pred || m_preds_with_facts.contains(pred)) { continue; } @@ -532,7 +532,7 @@ namespace datalog { } else { inlining_candidate = nullptr; - + for (unsigned ri = 0; ri < rule_cnt; ++ri) { rule * pred_rule = pred_rules[ri]; if (!m_unifier.unify_rules(*r, ti, *pred_rule)) { @@ -540,9 +540,9 @@ namespace datalog { continue; } if (inlining_candidate != nullptr) { - // We have two rules that can be inlined into the current + // We have two rules that can be inlined into the current // tail predicate. In this situation we don't do inlinning - // on this tail atom, as we don't want the overall number + // on this tail atom, as we don't want the overall number // of rules to increase. goto process_next_tail; } @@ -608,14 +608,14 @@ namespace datalog { P(1,x) :- P(1,z), phi(x,y), psi(y,z) - whenever P(0,x) is not unifiable with the + whenever P(0,x) is not unifiable with the body of the rule where it appears (P(1,z)) - and P(0,x) is unifiable with at most one (?) + and P(0,x) is unifiable with at most one (?) other rule (and it does not occur negatively). */ bool mk_rule_inliner::visitor::operator()(expr* e) { m_unifiers.append(m_positions.find(e)); - TRACE("dl", + TRACE("dl", tout << "unifier: " << (m_unifiers.empty()?0:m_unifiers.back()); tout << " num unifiers: " << m_unifiers.size(); tout << " num positions: " << m_positions.find(e).size() << "\n"; @@ -640,7 +640,7 @@ namespace datalog { } unsigned_vector const& mk_rule_inliner::visitor::del_position(expr* e, unsigned j) { - obj_map::obj_map_entry * et = m_positions.find_core(e); + obj_map::obj_map_entry * et = m_positions.find_core(e); SASSERT(et && et->get_data().m_value.contains(j)); et->get_data().m_value.erase(j); return et->get_data().m_value; @@ -654,7 +654,7 @@ namespace datalog { m_head_visitor.add_position(head, i); m_head_index.insert(head); m_pinned.push_back(r); - + if (source.is_output_predicate(headd) || m_preds_with_facts.contains(headd)) { can_remove.set(i, false); @@ -667,10 +667,10 @@ namespace datalog { m_tail_visitor.add_position(tail, i); m_tail_index.insert(tail); } - bool can_exp = + bool can_exp = tl_sz == 1 - && r->get_positive_tail_size() == 1 - && !m_preds_with_facts.contains(r->get_decl(0)) + && r->get_positive_tail_size() == 1 + && !m_preds_with_facts.contains(r->get_decl(0)) && !source.is_output_predicate(r->get_decl(0)); can_expand.set(i, can_exp); } @@ -682,14 +682,14 @@ namespace datalog { for (unsigned j = 0; j < tl_sz; ++j) { app* tail = r->get_tail(j); m_tail_visitor.del_position(tail, i); - } + } } #define PRT(_x_) ((_x_)?"T":"F") bool mk_rule_inliner::inline_linear(scoped_ptr& rules) { - bool done_something = false; + bool done_something = false; unsigned sz = rules->get_num_rules(); m_head_visitor.reset(sz); @@ -704,7 +704,7 @@ namespace datalog { acc.push_back(rules->get_rule(i)); } - // set up unification index. + // set up unification index. svector& can_remove = m_head_visitor.can_remove(); svector& can_expand = m_head_visitor.can_expand(); @@ -729,7 +729,7 @@ namespace datalog { svector valid; valid.reset(); - valid.resize(sz, true); + valid.resize(sz, true); bool allow_branching = m_context.get_params().xform_inline_linear_branch(); @@ -738,9 +738,9 @@ namespace datalog { while (true) { rule_ref r(acc[i].get(), m_rm); - + TRACE("dl", r->display(m_context, tout << "processing: " << i << "\n");); - + if (!valid.get(i)) { TRACE("dl", tout << "invalid: " << i << "\n";); break; @@ -762,9 +762,9 @@ namespace datalog { TRACE("dl", tout << PRT(can_remove.get(j)) << " " << PRT(valid.get(j)) << " " << PRT(i != j) << "\n";); break; } - + rule* r2 = acc[j].get(); - + // check that the head of r2 only unifies with this single body position. TRACE("dl", output_predicate(m_context, r2->get_head(), tout << "unify head: "); tout << "\n";); m_tail_visitor.reset(); @@ -776,7 +776,7 @@ namespace datalog { TRACE("dl", tout << "too many tails " << num_tail_unifiers << "\n";); break; } - + rule_ref rl_res(m_rm); if (!try_to_inline_rule(*r.get(), *r2, 0, rl_res)) { TRACE("dl", r->display(m_context, tout << "inlining failed\n"); r2->display(m_context, tout); ); @@ -787,12 +787,12 @@ namespace datalog { del_rule(r, i); add_rule(*rules, rl_res.get(), i); - + r = rl_res; acc[i] = r.get(); can_expand.set(i, can_expand.get(j)); - + if (num_tail_unifiers == 1) { TRACE("dl", tout << "setting invalid: " << j << "\n";); valid.set(j, false); @@ -815,22 +815,22 @@ namespace datalog { res->inherit_predicates(*rules); TRACE("dl", res->display(tout);); rules = res.detach(); - } + } return done_something; } rule_set * mk_rule_inliner::operator()(rule_set const & source) { bool something_done = false; - ref hsmc; + ref hsmc; if (source.get_num_rules() == 0) { return nullptr; } - for (rule const* r : source) - if (has_quantifier(*r)) - return nullptr; + for (rule const* r : source) + if (has_quantifier(*r)) + return nullptr; if (m_context.get_model_converter()) { hsmc = alloc(horn_subsume_model_converter, m); @@ -841,15 +841,15 @@ namespace datalog { if (m_context.get_params().xform_inline_eager()) { TRACE("dl", source.display(tout << "before eager inlining\n");); - plan_inlining(source); - something_done = transform_rules(source, *res); + plan_inlining(source); + something_done = transform_rules(source, *res); VERIFY(res->close()); //this transformation doesn't break the negation stratification // try eager inlining if (do_eager_inlining(res)) { something_done = true; - } + } TRACE("dl", res->display(tout << "after eager inlining\n");); - } + } if (something_done) { res->inherit_predicates(source); } @@ -870,6 +870,5 @@ namespace datalog { return res.detach(); } - -}; +}; diff --git a/src/muz/transforms/dl_mk_scale.cpp b/src/muz/transforms/dl_mk_scale.cpp index 0144948bd..9ceaeeab3 100644 --- a/src/muz/transforms/dl_mk_scale.cpp +++ b/src/muz/transforms/dl_mk_scale.cpp @@ -18,7 +18,7 @@ Revision History: #include "muz/transforms/dl_mk_scale.h" #include "muz/base/dl_context.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { @@ -37,7 +37,7 @@ namespace datalog { m_trail.push_back(new_f); m_new2old.insert(new_f, old_f); } - + void get_units(obj_map& units) override { units.reset(); } void operator()(model_ref& md) override { @@ -74,7 +74,7 @@ namespace datalog { old_model->register_decl(old_p, old_fi); } } - + // register values that have not been scaled. unsigned sz = md->get_num_constants(); for (unsigned i = 0; i < sz; ++i) { @@ -111,12 +111,12 @@ namespace datalog { m_ctx(ctx), a(m), m_trail(m), - m_eqs(m) { + m_eqs(m) { } - mk_scale::~mk_scale() { + mk_scale::~mk_scale() { } - + rule_set * mk_scale::operator()(rule_set const & source) { if (!m_ctx.scale()) { return nullptr; @@ -135,7 +135,7 @@ namespace datalog { } m_mc = smc.get(); - for (unsigned i = 0; i < sz; ++i) { + for (unsigned i = 0; i < sz; ++i) { rule & r = *source.get_rule(i); unsigned utsz = r.get_uninterpreted_tail_size(); unsigned tsz = r.get_tail_size(); @@ -157,10 +157,10 @@ namespace datalog { tail.push_back(a.mk_gt(m.mk_var(num_vars, a.mk_real()), a.mk_numeral(rational(0), false))); neg.resize(tail.size(), false); new_rule = rm.mk(new_pred, tail.size(), tail.c_ptr(), neg.c_ptr(), r.name(), true); - result->add_rule(new_rule); + result->add_rule(new_rule); if (source.is_output_predicate(r.get_decl())) { result->set_output_predicate(new_rule->get_decl()); - } + } } TRACE("dl", result->display(tout);); if (m_mc) { @@ -227,7 +227,7 @@ namespace datalog { a.is_lt(e) || a.is_gt(e)) { expr_ref_vector args(m); for (unsigned i = 0; i < ap->get_num_args(); ++i) { - args.push_back(linearize(sigma_idx, ap->get_arg(i))); + args.push_back(linearize(sigma_idx, ap->get_arg(i))); } result = m.mk_app(ap->get_decl(), args.size(), args.c_ptr()); } diff --git a/src/muz/transforms/dl_mk_subsumption_checker.cpp b/src/muz/transforms/dl_mk_subsumption_checker.cpp index 56883460a..da41b4ba4 100644 --- a/src/muz/transforms/dl_mk_subsumption_checker.cpp +++ b/src/muz/transforms/dl_mk_subsumption_checker.cpp @@ -24,7 +24,7 @@ Revision History: #include "ast/rewriter/rewriter.h" #include "ast/rewriter/rewriter_def.h" #include "muz/transforms/dl_mk_subsumption_checker.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" #include "tactic/generic_model_converter.h" @@ -39,8 +39,8 @@ namespace datalog { bool mk_subsumption_checker::is_total_rule(const rule * r) { - if (r->get_tail_size() != 0) { - return false; + if (r->get_tail_size() != 0) { + return false; } unsigned pt_len = r->get_positive_tail_size(); @@ -113,7 +113,7 @@ namespace datalog { } - bool mk_subsumption_checker::transform_rule(rule * r, + bool mk_subsumption_checker::transform_rule(rule * r, rule_subsumption_index& subs_index, rule_ref & res) { unsigned u_len = r->get_uninterpreted_tail_size(); @@ -133,7 +133,7 @@ namespace datalog { if(m_total_relations.contains(tail_atom->get_decl()) || subs_index.is_subsumed(tail_atom)) { if(neg) { - //rule contains negated total relation, this means that it is unsatisfiable + //rule contains negated total relation, this means that it is unsatisfiable //and can be removed return false; } @@ -143,8 +143,8 @@ namespace datalog { } } if(!neg && head.get()==tail_atom) { - //rule contains its head positively in the tail, therefore - //it will never add any new facts to the relation, so it + //rule contains its head positively in the tail, therefore + //it will never add any new facts to the relation, so it //can be removed return false; } @@ -197,9 +197,9 @@ namespace datalog { if (m_total_relations.contains(head_pred)) { if (!orig.is_output_predicate(head_pred) || total_relations_with_included_rules.contains(head_pred)) { - //We just skip definitions of total non-output relations as + //We just skip definitions of total non-output relations as //we'll eliminate them from the problem. - //We also skip rules of total output relations for which we have + //We also skip rules of total output relations for which we have //already output the rule which implies their totality. modified = true; continue; @@ -286,7 +286,7 @@ namespace datalog { obj_hashtable * head_store; if(m_ground_unconditional_rule_heads.find(pred, head_store)) { //Some relations may receive facts by ground unconditioned rules. - //We scanned for those earlier, so now we check whether we cannot get a + //We scanned for those earlier, so now we check whether we cannot get a //better estimate of relation size from these. unsigned gnd_rule_cnt = head_store->size(); @@ -334,7 +334,7 @@ namespace datalog { rule_set * mk_subsumption_checker::operator()(rule_set const & source) { // TODO mc - if (!m_context.get_params ().xform_subsumption_checker()) + if (!m_context.get_params ().xform_subsumption_checker()) return nullptr; m_have_new_total_rule = false; @@ -366,6 +366,5 @@ namespace datalog { return res; } - -}; +}; diff --git a/src/muz/transforms/dl_transforms.cpp b/src/muz/transforms/dl_transforms.cpp index d456d95f9..d4b9506e8 100644 --- a/src/muz/transforms/dl_transforms.cpp +++ b/src/muz/transforms/dl_transforms.cpp @@ -35,7 +35,7 @@ Revision History: #include "muz/transforms/dl_mk_scale.h" #include "muz/transforms/dl_mk_array_eq_rewrite.h" #include "muz/transforms/dl_mk_array_instantiation.h" -#include "muz/base/fixedpoint_params.hpp" +#include "muz/base/fp_params.hpp" namespace datalog { @@ -72,7 +72,7 @@ namespace datalog { transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34970)); transf.register_plugin(alloc(datalog::mk_coi_filter, ctx, 34960)); transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx, 34950)); - + if (ctx.get_params().datalog_subsumption()) { transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34940)); transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34930)); diff --git a/src/nlsat/nlsat_explain.cpp b/src/nlsat/nlsat_explain.cpp index 2278e53dd..a93935fb6 100644 --- a/src/nlsat/nlsat_explain.cpp +++ b/src/nlsat/nlsat_explain.cpp @@ -216,9 +216,10 @@ namespace nlsat { max_var(p) must be assigned in the current interpretation. */ int sign(polynomial_ref const & p) { - TRACE("nlsat_explain", tout << "p: " << p << " var: " << max_var(p) << "\n";); SASSERT(max_var(p) == null_var || m_assignment.is_assigned(max_var(p))); - return m_am.eval_sign_at(p, m_assignment); + int s = m_am.eval_sign_at(p, m_assignment); + TRACE("nlsat_explain", tout << "p: " << p << " var: " << max_var(p) << " sign: " << s << "\n";); + return s; } /** @@ -574,7 +575,7 @@ namespace nlsat { if (is_const(p)) return; if (m_factor) { - TRACE("nlsat_explain", tout << "adding factors of\n"; display(tout, p); tout << "\n";); + TRACE("nlsat_explain", display(tout << "adding factors of\n", p); tout << "\n";); factor(p, m_factors); polynomial_ref f(m_pm); for (unsigned i = 0; i < m_factors.size(); i++) { @@ -1452,12 +1453,11 @@ namespace nlsat { SASSERT(check_already_added()); SASSERT(num > 0); TRACE("nlsat_explain", tout << "[explain] set of literals is infeasible in the current interpretation\n"; display(tout, num, ls);); - // exit(0); m_result = &result; process(num, ls); reset_already_added(); m_result = nullptr; - TRACE("nlsat_explain", tout << "[explain] result\n"; display(tout, result);); + TRACE("nlsat_explain", display(tout << "[explain] result\n", result);); CASSERT("nlsat", check_already_added()); } @@ -1466,7 +1466,12 @@ namespace nlsat { m_result = &result; svector lits; - TRACE("nlsat", tout << "project x" << x << "\n"; m_solver.display(tout);); + TRACE("nlsat", tout << "project x" << x << "\n"; + for (unsigned i = 0; i < num; ++i) { + m_solver.display(tout, ls[i]) << " "; + } + tout << "\n"; + m_solver.display(tout);); DEBUG_CODE( for (unsigned i = 0; i < num; ++i) { @@ -1509,8 +1514,15 @@ namespace nlsat { result.set(i, ~result[i]); } DEBUG_CODE( - for (unsigned i = 0; i < result.size(); ++i) { - SASSERT(l_true == m_solver.value(result[i])); + TRACE("nlsat", + for (literal l : result) { + m_solver.display(tout << " ", l); + } + tout << "\n"; + ); + for (literal l : result) { + CTRACE("nlsat", l_true != m_solver.value(l), m_solver.display(tout, l) << " " << m_solver.value(l) << "\n";); + SASSERT(l_true == m_solver.value(l)); }); } @@ -1621,21 +1633,21 @@ namespace nlsat { roots.reset(); m_am.isolate_roots(p, undef_var_assignment(m_assignment, x), roots); bool glb_valid = false, lub_valid = false; - for (unsigned j = 0; j < roots.size(); ++j) { - int s = m_am.compare(x_val, roots[j]); + for (auto const& r : roots) { + int s = m_am.compare(x_val, r); SASSERT(s != 0); + + if (s < 0 && (!lub_valid || m_am.lt(r, lub))) { + lub_index = i; + m_am.set(lub, r); + } + + if (s > 0 && (!glb_valid || m_am.lt(glb, r))) { + glb_index = i; + m_am.set(glb, r); + } lub_valid |= s < 0; glb_valid |= s > 0; - - if (s < 0 && m_am.lt(roots[j], lub)) { - lub_index = i; - m_am.set(lub, roots[j]); - } - - if (s > 0 && m_am.lt(glb, roots[j])) { - glb_index = i; - m_am.set(glb, roots[j]); - } } if (glb_valid) { ++num_glb; @@ -1701,6 +1713,7 @@ namespace nlsat { } void project_pairs(var x, unsigned idx, polynomial_ref_vector const& ps) { + TRACE("nlsat_explain", tout << "project pairs\n";); polynomial_ref p(m_pm); p = ps.get(idx); for (unsigned i = 0; i < ps.size(); ++i) { @@ -1725,11 +1738,13 @@ namespace nlsat { void solve_eq(var x, unsigned idx, polynomial_ref_vector const& ps) { polynomial_ref p(m_pm), A(m_pm), B(m_pm), C(m_pm), D(m_pm), E(m_pm), q(m_pm), r(m_pm); - polynomial_ref_vector qs(m_pm); + polynomial_ref_vector As(m_pm), Bs(m_pm); p = ps.get(idx); SASSERT(degree(p, x) == 1); A = m_pm.coeff(p, x, 1); B = m_pm.coeff(p, x, 0); + As.push_back(m_pm.mk_const(rational(1))); + Bs.push_back(m_pm.mk_const(rational(1))); B = neg(B); TRACE("nlsat_explain", tout << "p: " << p << " A: " << A << " B: " << B << "\n";); // x = B/A @@ -1740,20 +1755,21 @@ namespace nlsat { D = m_pm.mk_const(rational(1)); E = D; r = m_pm.mk_zero(); - for (unsigned j = 0; j <= d; ++j) { - qs.push_back(D); - D = D*A; + for (unsigned j = As.size(); j <= d; ++j) { + D = As.back(); As.push_back(A * D); + D = Bs.back(); Bs.push_back(B * D); } for (unsigned j = 0; j <= d; ++j) { // A^d*p0 + A^{d-1}*B*p1 + ... + B^j*A^{d-j}*pj + ... + B^d*p_d C = m_pm.coeff(q, x, j); + TRACE("nlsat_explain", tout << "coeff: q" << j << ": " << C << "\n";); if (!is_zero(C)) { - D = qs.get(d-j); + D = As.get(d - j); + E = Bs.get(j); r = r + D*E*C; } - E = E*B; } - TRACE("nlsat_explain", tout << "q: " << q << " r: " << r << "\n";); + TRACE("nlsat_explain", tout << "p: " << p << " q: " << q << " r: " << r << "\n";); ensure_sign(r); } else { diff --git a/src/nlsat/nlsat_scoped_literal_vector.h b/src/nlsat/nlsat_scoped_literal_vector.h index 61760f06d..dffaa169f 100644 --- a/src/nlsat/nlsat_scoped_literal_vector.h +++ b/src/nlsat/nlsat_scoped_literal_vector.h @@ -34,9 +34,8 @@ namespace nlsat { bool empty() const { return m_lits.empty(); } literal operator[](unsigned i) const { return m_lits[i]; } void reset() { - unsigned sz = m_lits.size(); - for (unsigned i = 0; i < sz; i++) { - m_solver.dec_ref(m_lits[i]); + for (literal l : m_lits) { + m_solver.dec_ref(l); } m_lits.reset(); } @@ -50,6 +49,8 @@ namespace nlsat { m_lits[i] = l; } literal const * c_ptr() const { return m_lits.c_ptr(); } + literal const * begin() const { return m_lits.begin(); } + literal const * end() const { return m_lits.end(); } void shrink(unsigned new_sz) { SASSERT(new_sz <= m_lits.size()); unsigned sz = m_lits.size(); diff --git a/src/nlsat/nlsat_solver.cpp b/src/nlsat/nlsat_solver.cpp index 9767448b1..f430f3a0c 100644 --- a/src/nlsat/nlsat_solver.cpp +++ b/src/nlsat/nlsat_solver.cpp @@ -270,7 +270,7 @@ namespace nlsat { TRACE("ref", tout << "inc: " << b << "\n";); if (b == null_bool_var) return; - if (m_atoms[b] == 0) + if (m_atoms[b] == nullptr) return; m_atoms[b]->inc_ref(); } @@ -395,7 +395,7 @@ namespace nlsat { bool_var mk_bool_var_core() { bool_var b = m_bid_gen.mk(); m_num_bool_vars++; - m_atoms .setx(b, 0, 0); + m_atoms .setx(b, nullptr, nullptr); m_bvalues .setx(b, l_undef, l_undef); m_levels .setx(b, UINT_MAX, UINT_MAX); m_justifications.setx(b, null_justification, null_justification); @@ -469,7 +469,7 @@ namespace nlsat { //SASSERT(m_bvalues[b] == l_undef); m_num_bool_vars--; m_dead[b] = true; - m_atoms[b] = 0; + m_atoms[b] = nullptr; m_bid_gen.recycle(b); } @@ -494,6 +494,7 @@ namespace nlsat { void del(atom * a) { if (a == nullptr) return ; + TRACE("nlsat", display(tout << "del: b" << a->m_bool_var << " ", *a) << "\n";); if (a->is_ineq_atom()) del(to_ineq_atom(a)); else @@ -555,6 +556,7 @@ namespace nlsat { bool_var b = mk_bool_var_core(); m_atoms[b] = atom; atom->m_bool_var = b; + TRACE("nlsat", display(tout << "create: b" << atom->m_bool_var << " ", *atom) << "\n";); return b; } } @@ -749,7 +751,7 @@ namespace nlsat { m_levels[b] = UINT_MAX; del_jst(m_allocator, m_justifications[b]); m_justifications[b] = null_justification; - if (m_atoms[b] == 0 && b < m_bk) + if (m_atoms[b] == nullptr && b < m_bk) m_bk = b; } @@ -895,7 +897,7 @@ namespace nlsat { \brief Assign literal using the given justification */ void assign(literal l, justification j) { - TRACE("nlsat", tout << "assigning literal:\n"; display(tout, l); + TRACE("nlsat", tout << "assigning literal: "; display(tout, l); tout << "\njustification kind: " << j.get_kind() << "\n";); SASSERT(assigned_value(l) == l_undef); SASSERT(j != null_justification); @@ -926,6 +928,7 @@ namespace nlsat { */ lbool value(literal l) { lbool val = assigned_value(l); + TRACE("nlsat_verbose", display(tout << " assigned value " << val << " for ", l) << "\n";); if (val != l_undef) { return val; } @@ -941,7 +944,7 @@ namespace nlsat { val = to_lbool(m_evaluator.eval(a, l.sign())); TRACE("value_bug", tout << "value of: "; display(tout, l); tout << " := " << val << "\n"; tout << "xk: " << m_xk << ", a->max_var(): " << a->max_var() << "\n"; - display_assignment(tout);); + display_assignment(tout);); return val; } @@ -2011,9 +2014,9 @@ namespace nlsat { } bool can_reorder() const { - for (unsigned i = 0; i < m_atoms.size(); ++i) { - if (m_atoms[i]) { - if (m_atoms[i]->is_root_atom()) return false; + for (atom * a : m_atoms) { + if (a) { + if (a->is_root_atom()) return false; } } return true; @@ -2100,16 +2103,13 @@ namespace nlsat { \brief After variable reordering some lemmas containing root atoms may be ill-formed. */ void del_ill_formed_lemmas() { - unsigned sz = m_learned.size(); unsigned j = 0; - for (unsigned i = 0; i < sz; i++) { - clause * c = m_learned[i]; + for (clause* c : m_learned) { if (ill_formed(*c)) { del_clause(c); } else { - m_learned[j] = c; - j++; + m_learned[j++] = c; } } m_learned.shrink(j); @@ -2119,9 +2119,8 @@ namespace nlsat { \brief Return true if the clause contains an ill formed root atom */ bool ill_formed(clause const & c) { - unsigned sz = c.size(); - for (unsigned i = 0; i < sz; i++) { - bool_var b = c[i].var(); + for (literal lit : c) { + bool_var b = lit.var(); atom * a = m_atoms[b]; if (a == nullptr) continue; @@ -2139,19 +2138,16 @@ namespace nlsat { void reinit_cache() { reinit_cache(m_clauses); reinit_cache(m_learned); - for (unsigned i = 0; i < m_atoms.size(); ++i) { - reinit_cache(m_atoms[i]); - } + for (atom* a : m_atoms) + reinit_cache(a); } void reinit_cache(clause_vector const & cs) { - unsigned sz = cs.size(); - for (unsigned i = 0; i < sz; i++) - reinit_cache(*(cs[i])); + for (clause* c : cs) + reinit_cache(*c); } void reinit_cache(clause const & c) { - unsigned sz = c.size(); - for (unsigned i = 0; i < sz; i++) - reinit_cache(c[i]); + for (literal l : c) + reinit_cache(l); } void reinit_cache(literal l) { bool_var b = l.var(); @@ -2566,9 +2562,12 @@ namespace nlsat { std::ostream& display_bool_assignment(std::ostream & out) const { unsigned sz = m_atoms.size(); for (bool_var b = 0; b < sz; b++) { - if (m_atoms[b] == 0 && m_bvalues[b] != l_undef) { + if (m_atoms[b] == nullptr && m_bvalues[b] != l_undef) { out << "b" << b << " -> " << (m_bvalues[b] == l_true ? "true" : "false") << "\n"; } + else if (m_atoms[b] != nullptr && m_bvalues[b] != l_undef) { + display(out << "b" << b << " ", *m_atoms[b]) << " -> " << (m_bvalues[b] == l_true ? "true" : "false") << "\n"; + } } TRACE("nlsat_bool_assignment", for (bool_var b = 0; b < sz; b++) { @@ -3035,7 +3034,7 @@ namespace nlsat { std::ostream& display_smt2_bool_decls(std::ostream & out) const { unsigned sz = m_atoms.size(); for (unsigned i = 0; i < sz; i++) { - if (m_atoms[i] == 0) + if (m_atoms[i] == nullptr) out << "(declare-fun b" << i << " () Bool)\n"; } return out; @@ -3162,13 +3161,24 @@ namespace nlsat { void solver::get_bvalues(svector& vs) { vs.reset(); - vs.append(m_imp->m_bvalues); + unsigned sz = m_imp->m_bvalues.size(); + for (bool_var b = 0; b < sz; ++b) { + if (m_imp->m_atoms[b] == nullptr) { + vs.push_back(m_imp->m_bvalues[b]); + } + else { + vs.push_back(l_undef); // don't save values from atoms. + } + } + TRACE("nlsat", display(tout);); } void solver::set_bvalues(svector const& vs) { + TRACE("nlsat", display(tout);); m_imp->m_bvalues.reset(); m_imp->m_bvalues.append(vs); m_imp->m_bvalues.resize(m_imp->m_atoms.size(), l_undef); + TRACE("nlsat", display(tout);); } var solver::mk_var(bool is_int) { @@ -3199,27 +3209,28 @@ namespace nlsat { return m_imp->mk_clause(num_lits, lits, a); } - void solver::display(std::ostream & out) const { - m_imp->display(out); + std::ostream& solver::display(std::ostream & out) const { + return m_imp->display(out); } - void solver::display(std::ostream & out, literal l) const { - m_imp->display(out, l); + std::ostream& solver::display(std::ostream & out, literal l) const { + return m_imp->display(out, l); } - void solver::display(std::ostream & out, unsigned n, literal const* ls) const { + std::ostream& solver::display(std::ostream & out, unsigned n, literal const* ls) const { for (unsigned i = 0; i < n; ++i) { display(out, ls[i]); out << "; "; } + return out; } - void solver::display(std::ostream & out, var x) const { - m_imp->m_display_var(out, x); + std::ostream& solver::display(std::ostream & out, var x) const { + return m_imp->m_display_var(out, x); } - void solver::display(std::ostream & out, atom const& a) const { - m_imp->display(out, a, m_imp->m_display_var); + std::ostream& solver::display(std::ostream & out, atom const& a) const { + return m_imp->display(out, a, m_imp->m_display_var); } display_var_proc const & solver::display_proc() const { diff --git a/src/nlsat/nlsat_solver.h b/src/nlsat/nlsat_solver.h index e7b250f64..4ba1225bd 100644 --- a/src/nlsat/nlsat_solver.h +++ b/src/nlsat/nlsat_solver.h @@ -225,21 +225,21 @@ namespace nlsat { /** \brief Display solver's state. */ - void display(std::ostream & out) const; + std::ostream& display(std::ostream & out) const; /** \brief Display literal */ - void display(std::ostream & out, literal l) const; + std::ostream& display(std::ostream & out, literal l) const; - void display(std::ostream & out, unsigned n, literal const* ls) const; + std::ostream& display(std::ostream & out, unsigned n, literal const* ls) const; - void display(std::ostream & out, atom const& a) const; + std::ostream& display(std::ostream & out, atom const& a) const; /** \brief Display variable */ - void display(std::ostream & out, var x) const; + std::ostream& display(std::ostream & out, var x) const; display_var_proc const & display_proc() const; }; diff --git a/src/nlsat/tactic/goal2nlsat.cpp b/src/nlsat/tactic/goal2nlsat.cpp index 133652c1d..42ae36564 100644 --- a/src/nlsat/tactic/goal2nlsat.cpp +++ b/src/nlsat/tactic/goal2nlsat.cpp @@ -198,7 +198,6 @@ struct goal2nlsat::imp { throw tactic_exception("apply simplify before applying nlsat"); case OP_AND: case OP_OR: - case OP_IFF: case OP_XOR: case OP_NOT: case OP_IMPLIES: diff --git a/src/nlsat/tactic/nlsat_tactic.cpp b/src/nlsat/tactic/nlsat_tactic.cpp index 1392df3f6..5e536bbe6 100644 --- a/src/nlsat/tactic/nlsat_tactic.cpp +++ b/src/nlsat/tactic/nlsat_tactic.cpp @@ -83,9 +83,8 @@ class nlsat_tactic : public tactic { bool eval_model(model& model, goal& g) { unsigned sz = g.size(); for (unsigned i = 0; i < sz; i++) { - expr_ref val(m); - if (model.eval(g.form(i), val) && !m.is_true(val)) { - TRACE("nlsat", tout << mk_pp(g.form(i), m) << " -> " << val << "\n";); + if (!model.is_true(g.form(i))) { + TRACE("nlsat", tout << mk_pp(g.form(i), m) << " -> " << model(g.form(i)) << "\n";); return false; } } diff --git a/src/opt/maxres.cpp b/src/opt/maxres.cpp index d49044d8d..f52c56a60 100644 --- a/src/opt/maxres.cpp +++ b/src/opt/maxres.cpp @@ -88,7 +88,7 @@ private: expr_ref_vector m_asms; expr_ref_vector m_defs; obj_map m_asm2weight; - ptr_vector m_new_core; + expr_ref_vector m_new_core; mus m_mus; expr_ref_vector m_trail; strategy_t m_st; @@ -119,6 +119,7 @@ public: maxsmt_solver_base(c, ws, soft), m_index(index), m_B(m), m_asms(m), m_defs(m), + m_new_core(m), m_mus(c.get_solver()), m_trail(m), m_st(st), @@ -174,10 +175,11 @@ public: void new_assumption(expr* e, rational const& w) { IF_VERBOSE(13, verbose_stream() << "new assumption " << mk_pp(e, m) << " " << w << "\n";); - TRACE("opt", tout << "insert: " << mk_pp(e, m) << " : " << w << "\n";); m_asm2weight.insert(e, w); m_asms.push_back(e); m_trail.push_back(e); + TRACE("opt", tout << "insert: " << mk_pp(e, m) << " : " << w << "\n"; + tout << m_asms << " " << "\n"; ); } void trace() { @@ -191,7 +193,7 @@ public: trace(); if (is_sat != l_true) return is_sat; while (m_lower < m_upper) { - TRACE("opt", + TRACE("opt_verbose", display_vec(tout, m_asms); s().display(tout); tout << "\n"; @@ -203,7 +205,12 @@ public: } switch (is_sat) { case l_true: - SASSERT(is_true(m_asms)); + CTRACE("opt", !m_model->is_true(m_asms), + tout << *m_model; + tout << "assumptions: "; + for (expr* a : m_asms) tout << mk_pp(a, m) << " -> " << (*m_model)(a) << " "; + tout << "\n";); + SASSERT(m_model->is_true(m_asms)); found_optimum(); return l_true; case l_false: @@ -275,8 +282,7 @@ public: /** Give preference to cores that have large minmal values. */ - sort_assumptions(asms); - + sort_assumptions(asms); m_last_index = std::min(m_last_index, asms.size()-1); m_last_index = 0; unsigned index = m_last_index>0?m_last_index-1:0; @@ -289,8 +295,6 @@ public: index = next_index(asms, index); } first = false; - IF_VERBOSE(3, verbose_stream() << "hill climb " << index << "\n";); - // IF_VERBOSE(3, verbose_stream() << "weight: " << get_weight(asms[0].get()) << " " << get_weight(asms[index-1].get()) << " num soft: " << index << "\n";); m_last_index = index; is_sat = check_sat(index, asms.c_ptr()); } @@ -306,8 +310,9 @@ public: if (r == l_true) { model_ref mdl; s().get_model(mdl); + TRACE("opt", tout << *mdl;); if (mdl.get()) { - update_assignment(mdl.get()); + update_assignment(mdl); } } return r; @@ -316,10 +321,10 @@ public: void found_optimum() { IF_VERBOSE(1, verbose_stream() << "found optimum\n";); m_lower.reset(); - for (unsigned i = 0; i < m_soft.size(); ++i) { - m_assignment[i] = is_true(m_soft[i]); - if (!m_assignment[i]) { - m_lower += m_weights[i]; + for (soft& s : m_soft) { + s.is_true = m_model->is_true(s.s); + if (!s.is_true) { + m_lower += s.weight; } } m_upper = m_lower; @@ -346,16 +351,17 @@ public: lbool get_cores(vector& cores) { // assume m_s is unsat. lbool is_sat = l_false; - expr_ref_vector asms(m_asms); cores.reset(); exprs core; while (is_sat == l_false) { core.reset(); - s().get_unsat_core(core); - // verify_core(core); + expr_ref_vector _core(m); + s().get_unsat_core(_core); model_ref mdl; get_mus_model(mdl); - is_sat = minimize_core(core); + is_sat = minimize_core(_core); + core.append(_core.size(), _core.c_ptr()); + // verify_core(core); ++m_stats.m_num_cores; if (is_sat != l_true) { IF_VERBOSE(100, verbose_stream() << "(opt.maxres minimization failed)\n";); @@ -367,23 +373,24 @@ public: m_lower = m_upper; return l_true; } + // 1. remove all core literals from m_asms + // 2. re-add literals of higher weight than min-weight. + // 3. 'core' stores the core literals that are + // re-encoded as assumptions, afterwards + remove_soft(core, m_asms); split_core(core); cores.push_back(core); - if (core.size() >= m_max_core_size) { - break; - } - if (cores.size() >= m_max_num_cores) { - break; - } - remove_soft(core, asms); - is_sat = check_sat_hill_climb(asms); + + if (core.size() >= m_max_core_size) break; + if (cores.size() >= m_max_num_cores) break; + + is_sat = check_sat_hill_climb(m_asms); } + TRACE("opt", - tout << "num cores: " << cores.size() << "\n"; - for (unsigned i = 0; i < cores.size(); ++i) { - display_vec(tout, cores[i]); - } - tout << "num satisfying: " << asms.size() << "\n";); + tout << "sat: " << is_sat << " num cores: " << cores.size() << "\n"; + for (auto const& c : cores) display_vec(tout, c); + tout << "num assumptions: " << m_asms.size() << "\n";); return is_sat; } @@ -391,7 +398,7 @@ public: void get_current_correction_set(exprs& cs) { model_ref mdl; s().get_model(mdl); - update_assignment(mdl.get()); + update_assignment(mdl); get_current_correction_set(mdl.get(), cs); } @@ -399,10 +406,9 @@ public: cs.reset(); if (!mdl) return; for (expr* a : m_asms) { - if (is_false(mdl, a)) { + if (mdl->is_false(a)) { cs.push_back(a); } - TRACE("opt", expr_ref tmp(m); mdl->eval(a, tmp, true); tout << mk_pp(a, m) << ": " << tmp << "\n";); } TRACE("opt", display_vec(tout << "new correction set: ", cs);); } @@ -441,7 +447,7 @@ public: ++m_stats.m_num_cs; expr_ref fml(m), tmp(m); TRACE("opt", display_vec(tout << "corr_set: ", corr_set);); - remove_core(corr_set); + remove_soft(corr_set, m_asms); rational w = split_core(corr_set); cs_max_resolve(corr_set, w); IF_VERBOSE(2, verbose_stream() << "(opt.maxres.correction-set " << corr_set.size() << ")\n";); @@ -466,33 +472,28 @@ public: unsigned max_core_size(vector const& cores) { unsigned result = 0; - for (unsigned i = 0; i < cores.size(); ++i) { - result = std::max(cores[i].size(), result); + for (auto const& c : cores) { + result = std::max(c.size(), result); } return result; } void process_unsat(vector const& cores) { - for (unsigned i = 0; i < cores.size(); ++i) { - process_unsat(cores[i]); + for (auto const & c : cores) { + process_unsat(c); } } void update_model(expr* def, expr* value) { SASSERT(is_uninterp_const(def)); if (m_csmodel) { - expr_ref val(m); - SASSERT(m_csmodel.get()); - if (m_csmodel->eval(value, val, true)) { - m_csmodel->register_decl(to_app(def)->get_decl(), val); - } + m_csmodel->register_decl(to_app(def)->get_decl(), (*m_csmodel)(value)); } } void process_unsat(exprs const& core) { IF_VERBOSE(3, verbose_stream() << "(maxres cs model valid: " << (m_csmodel.get() != nullptr) << " cs size:" << m_correction_set_size << " core: " << core.size() << ")\n";); expr_ref fml(m); - remove_core(core); SASSERT(!core.empty()); rational w = core_weight(core); TRACE("opt", display_vec(tout << "minimized core: ", core);); @@ -533,12 +534,12 @@ public: w = m_mus.get_best_model(mdl); } if (mdl.get() && w < m_upper) { - update_assignment(mdl.get()); + update_assignment(mdl); } return nullptr != mdl.get(); } - lbool minimize_core(exprs& core) { + lbool minimize_core(expr_ref_vector& core) { if (core.empty()) { return l_true; } @@ -599,8 +600,7 @@ public: } void display(std::ostream& out) { - for (unsigned i = 0; i < m_asms.size(); ++i) { - expr* a = m_asms[i].get(); + for (expr * a : m_asms) { out << mk_pp(a, m) << " : " << get_weight(a) << "\n"; } } @@ -705,10 +705,11 @@ public: s().assert_expr(fml); } - void update_assignment(model* mdl) { + void update_assignment(model_ref & mdl) { + mdl->set_model_completion(true); unsigned correction_set_size = 0; - for (unsigned i = 0; i < m_asms.size(); ++i) { - if (is_false(mdl, m_asms[i].get())) { + for (expr* a : m_asms) { + if (mdl->is_false(a)) { ++correction_set_size; } } @@ -717,102 +718,81 @@ public: m_correction_set_size = correction_set_size; } + TRACE("opt", tout << *mdl;); + rational upper(0); - expr_ref tmp(m); - for (unsigned i = 0; i < m_soft.size(); ++i) { - if (!is_true(mdl, m_soft[i])) { - upper += m_weights[i]; + + for (soft& s : m_soft) { + TRACE("opt", tout << s.s << ": " << (*mdl)(s.s) << " " << s.weight << "\n";); + if (!mdl->is_true(s.s)) { + upper += s.weight; } } if (upper > m_upper) { + TRACE("opt", tout << "new upper: " << upper << " vs existing upper: " << m_upper << "\n";); return; } - if (!m_c.verify_model(m_index, mdl, upper)) { + if (!m_c.verify_model(m_index, mdl.get(), upper)) { return; } m_model = mdl; - m_c.model_updated(mdl); + m_c.model_updated(mdl.get()); - TRACE("opt", model_smt2_pp(tout << "updated model\n", m, *m_model, 0);); + TRACE("opt", tout << "updated upper: " << upper << "\nmodel\n" << *m_model;); - for (unsigned i = 0; i < m_soft.size(); ++i) { - m_assignment[i] = is_true(m_soft[i]); + for (soft& s : m_soft) { + s.is_true = m_model->is_true(s.s); } // DEBUG_CODE(verify_assignment();); m_upper = upper; + trace(); add_upper_bound_block(); - } void add_upper_bound_block() { if (!m_add_upper_bound_block) return; pb_util u(m); expr_ref_vector nsoft(m); + vector weights; expr_ref fml(m); - for (unsigned i = 0; i < m_soft.size(); ++i) { - nsoft.push_back(mk_not(m, m_soft[i])); + for (soft& s : m_soft) { + nsoft.push_back(mk_not(m, s.s)); + weights.push_back(s.weight); } - fml = u.mk_lt(nsoft.size(), m_weights.c_ptr(), nsoft.c_ptr(), m_upper); + fml = u.mk_lt(nsoft.size(), weights.c_ptr(), nsoft.c_ptr(), m_upper); TRACE("opt", tout << "block upper bound " << fml << "\n";);; s().assert_expr(fml); } - bool is_true(model* mdl, expr* e) { - expr_ref tmp(m); - return mdl->eval(e, tmp, true) && m.is_true(tmp); - } - - bool is_false(model* mdl, expr* e) { - expr_ref tmp(m); - return mdl->eval(e, tmp, true) && m.is_false(tmp); - } - - bool is_true(expr* e) { - return is_true(m_model.get(), e); - } - - bool is_true(expr_ref_vector const& es) { - unsigned i = 0; - for (; i < es.size() && is_true(es[i]); ++i) { } - CTRACE("opt", i < es.size(), tout << mk_pp(es[i], m) << "\n"; - model_smt2_pp(tout, m, *m_model, 0); - ); - return i == es.size(); - } - void remove_soft(exprs const& core, expr_ref_vector& asms) { - for (unsigned i = 0; i < asms.size(); ++i) { - if (core.contains(asms[i].get())) { - asms[i] = asms.back(); - asms.pop_back(); - --i; - } - } + TRACE("opt", tout << "before remove: " << asms << "\n";); + unsigned j = 0; + for (expr* a : asms) + if (!core.contains(a)) + asms[j++] = a; + asms.shrink(j); + TRACE("opt", tout << "after remove: " << asms << "\n";); } - void remove_core(exprs const& core) { - remove_soft(core, m_asms); - } - - virtual void updt_params(params_ref& p) { - maxsmt_solver_base::updt_params(p); - opt_params _p(p); - m_hill_climb = _p.maxres_hill_climb(); - m_add_upper_bound_block = _p.maxres_add_upper_bound_block(); - m_max_num_cores = _p.maxres_max_num_cores(); - m_max_core_size = _p.maxres_max_core_size(); - m_maximize_assignment = _p.maxres_maximize_assignment(); - m_max_correction_set_size = _p.maxres_max_correction_set_size(); - m_pivot_on_cs = _p.maxres_pivot_on_correction_set(); - m_wmax = _p.maxres_wmax(); - m_dump_benchmarks = _p.dump_benchmarks(); + virtual void updt_params(params_ref& _p) { + maxsmt_solver_base::updt_params(_p); + opt_params p(_p); + m_hill_climb = p.maxres_hill_climb(); + m_add_upper_bound_block = p.maxres_add_upper_bound_block(); + m_max_num_cores = p.maxres_max_num_cores(); + m_max_core_size = p.maxres_max_core_size(); + m_maximize_assignment = p.maxres_maximize_assignment(); + m_max_correction_set_size = p.maxres_max_correction_set_size(); + m_pivot_on_cs = p.maxres_pivot_on_correction_set(); + m_wmax = p.maxres_wmax(); + m_dump_benchmarks = p.dump_benchmarks(); } lbool init_local() { @@ -824,9 +804,8 @@ public: if (is_sat != l_true) { return is_sat; } - obj_map::iterator it = new_soft.begin(), end = new_soft.end(); - for (; it != end; ++it) { - add_soft(it->m_key, it->m_value); + for (auto const& kv : new_soft) { + add_soft(kv.m_key, kv.m_value); } m_max_upper = m_upper; m_found_feasible_optimum = false; @@ -839,10 +818,7 @@ public: virtual void commit_assignment() { if (m_found_feasible_optimum) { - TRACE("opt", tout << "Committing feasible solution\n"; - tout << m_defs; - tout << m_asms; - ); + TRACE("opt", tout << "Committing feasible solution\n" << m_defs << " " << m_asms;); s().assert_expr(m_defs); s().assert_expr(m_asms); } @@ -852,9 +828,7 @@ public: void verify_core(exprs const& core) { IF_VERBOSE(3, verbose_stream() << "verify core\n";); ref smt_solver = mk_smt_solver(m, m_params, symbol()); - for (unsigned i = 0; i < s().get_num_assertions(); ++i) { - smt_solver->assert_expr(s().get_assertion(i)); - } + smt_solver->assert_expr(s().get_assertions()); smt_solver->assert_expr(core); lbool is_sat = smt_solver->check_sat(0, nullptr); if (is_sat == l_true) { @@ -865,13 +839,11 @@ public: void verify_assignment() { IF_VERBOSE(1, verbose_stream() << "verify assignment\n";); ref smt_solver = mk_smt_solver(m, m_params, symbol()); - for (unsigned i = 0; i < s().get_num_assertions(); ++i) { - smt_solver->assert_expr(s().get_assertion(i)); - } + smt_solver->assert_expr(s().get_assertions()); expr_ref n(m); - for (unsigned i = 0; i < m_soft.size(); ++i) { - n = m_soft[i]; - if (!m_assignment[i]) { + for (soft& s : m_soft) { + n = s.s; + if (!s.is_true) { n = mk_not(m, n); } smt_solver->assert_expr(n); diff --git a/src/opt/maxsmt.cpp b/src/opt/maxsmt.cpp index afecf7d2b..1b44b578b 100644 --- a/src/opt/maxsmt.cpp +++ b/src/opt/maxsmt.cpp @@ -21,6 +21,7 @@ Notes: #include "opt/maxsmt.h" #include "opt/maxres.h" #include "opt/wmax.h" +#include "opt/opt_params.hpp" #include "ast/ast_pp.h" #include "util/uint_set.h" #include "opt/opt_context.h" @@ -33,16 +34,17 @@ Notes: namespace opt { maxsmt_solver_base::maxsmt_solver_base( - maxsat_context& c, vector const& ws, expr_ref_vector const& soft): + maxsat_context& c, vector const& ws, expr_ref_vector const& softs): m(c.get_manager()), m_c(c), - m_soft(soft), - m_weights(ws), m_assertions(m), m_trail(m) { c.get_base_model(m_model); SASSERT(m_model); updt_params(c.params()); + for (unsigned i = 0; i < ws.size(); ++i) { + m_soft.push_back(soft(expr_ref(softs.get(i), m), ws[i], false)); + } } void maxsmt_solver_base::updt_params(params_ref& p) { @@ -55,17 +57,21 @@ namespace opt { void maxsmt_solver_base::commit_assignment() { expr_ref tmp(m); + expr_ref_vector fmls(m); rational k(0), cost(0); - for (unsigned i = 0; i < m_soft.size(); ++i) { - if (get_assignment(i)) { - k += m_weights[i]; + vector weights; + for (soft const& s : m_soft) { + if (s.is_true) { + k += s.weight; } else { - cost += m_weights[i]; + cost += s.weight; } + weights.push_back(s.weight); + fmls.push_back(s.s); } pb_util pb(m); - tmp = pb.mk_ge(m_weights.size(), m_weights.c_ptr(), m_soft.c_ptr(), k); + tmp = pb.mk_ge(weights.size(), weights.c_ptr(), fmls.c_ptr(), k); TRACE("opt", tout << "cost: " << cost << "\n" << tmp << "\n";); s().assert_expr(tmp); } @@ -73,21 +79,14 @@ namespace opt { bool maxsmt_solver_base::init() { m_lower.reset(); m_upper.reset(); - m_assignment.reset(); - for (unsigned i = 0; i < m_weights.size(); ++i) { - expr_ref val(m); - if (!m_model->eval(m_soft[i], val)) return false; - m_assignment.push_back(m.is_true(val)); - if (!m_assignment.back()) { - m_upper += m_weights[i]; - } + for (soft& s : m_soft) { + s.is_true = m.is_true(s.s); + if (!s.is_true) m_upper += s.weight; } TRACE("opt", tout << "upper: " << m_upper << " assignments: "; - for (unsigned i = 0; i < m_weights.size(); ++i) { - tout << (m_assignment[i]?"T":"F"); - } + for (soft& s : m_soft) tout << (s.is_true?"T":"F"); tout << "\n";); return true; } @@ -142,6 +141,7 @@ namespace opt { maxsmt_solver_base::scoped_ensure_theory::scoped_ensure_theory(maxsmt_solver_base& s) { m_wth = s.ensure_wmax_theory(); } + maxsmt_solver_base::scoped_ensure_theory::~scoped_ensure_theory() { if (m_wth) { m_wth->reset_local(); @@ -160,11 +160,13 @@ namespace opt { lbool maxsmt_solver_base::find_mutexes(obj_map& new_soft) { m_lower.reset(); - for (unsigned i = 0; i < m_soft.size(); ++i) { - new_soft.insert(m_soft[i], m_weights[i]); + expr_ref_vector fmls(m); + for (soft& s : m_soft) { + new_soft.insert(s.s, s.weight); + fmls.push_back(s.s); } vector mutexes; - lbool is_sat = s().find_mutexes(m_soft, mutexes); + lbool is_sat = s().find_mutexes(fmls, mutexes); if (is_sat != l_true) { return is_sat; } @@ -231,9 +233,7 @@ namespace opt { m_msolver = nullptr; symbol const& maxsat_engine = m_c.maxsat_engine(); IF_VERBOSE(1, verbose_stream() << "(maxsmt)\n";); - TRACE("opt", tout << "maxsmt\n"; - s().display(tout); tout << "\n"; - ); + TRACE("opt_verbose", s().display(tout << "maxsmt\n") << "\n";); if (m_soft_constraints.empty() || maxsat_engine == symbol("maxres") || maxsat_engine == symbol::null) { m_msolver = mk_maxres(m_c, m_index, m_weights, m_soft_constraints); } @@ -409,6 +409,58 @@ namespace opt { m_c.model_updated(mdl); } + class solver_maxsat_context : public maxsat_context { + params_ref m_params; + solver_ref m_solver; + model_ref m_model; + ref m_fm; + symbol m_maxsat_engine; + public: + solver_maxsat_context(params_ref& p, solver* s, model * m): + m_params(p), + m_solver(s), + m_model(m), + m_fm(alloc(generic_model_converter, s->get_manager(), "maxsmt")) { + opt_params _p(p); + m_maxsat_engine = _p.maxsat_engine(); + } + generic_model_converter& fm() override { return *m_fm.get(); } + bool sat_enabled() const override { return false; } + solver& get_solver() override { return *m_solver.get(); } + ast_manager& get_manager() const override { return m_solver->get_manager(); } + params_ref& params() override { return m_params; } + void enable_sls(bool force) override { } // no op + symbol const& maxsat_engine() const override { return m_maxsat_engine; } + void get_base_model(model_ref& _m) override { _m = m_model; }; + smt::context& smt_context() override { + throw default_exception("stand-alone maxsat context does not support wmax"); + } + unsigned num_objectives() override { return 1; } + bool verify_model(unsigned id, model* mdl, rational const& v) override { return true; }; + void set_model(model_ref& _m) override { m_model = _m; } + void model_updated(model* mdl) override { } // no-op + }; - + lbool maxsmt_wrapper::operator()(vector>& soft) { + solver_maxsat_context ctx(m_params, m_solver.get(), m_model.get()); + maxsmt maxsmt(ctx, 0); + for (auto const& p : soft) { + maxsmt.add(p.first, p.second); + } + lbool r = maxsmt(); + if (r == l_true) { + ast_manager& m = m_solver->get_manager(); + svector labels; + maxsmt.get_model(m_model, labels); + // TBD: is m_fm applied or not? + unsigned j = 0; + for (auto const& p : soft) { + if (m_model->is_true(p.first)) { + soft[j++] = p; + } + } + soft.shrink(j); + } + return r; + } }; diff --git a/src/opt/maxsmt.h b/src/opt/maxsmt.h index 9e52481f2..b61d876b3 100644 --- a/src/opt/maxsmt.h +++ b/src/opt/maxsmt.h @@ -56,17 +56,26 @@ namespace opt { // class maxsmt_solver_base : public maxsmt_solver { protected: + struct soft { + expr_ref s; + rational weight; + bool is_true; + soft(expr_ref const& s, rational const& w, bool t): s(s), weight(w), is_true(t) {} + soft(soft const& other):s(other.s), weight(other.weight), is_true(other.is_true) {} + soft& operator=(soft const& other) { s = other.s; weight = other.weight; is_true = other.is_true; return *this; } + }; ast_manager& m; - maxsat_context& m_c; - const expr_ref_vector m_soft; - vector m_weights; + maxsat_context& m_c; + vector m_soft; expr_ref_vector m_assertions; expr_ref_vector m_trail; rational m_lower; rational m_upper; model_ref m_model; svector m_labels; - svector m_assignment; // truth assignment to soft constraints + //const expr_ref_vector m_soft; + //vector m_weights; + //svector m_assignment; // truth assignment to soft constraints params_ref m_params; // config public: @@ -75,7 +84,7 @@ namespace opt { ~maxsmt_solver_base() override {} rational get_lower() const override { return m_lower; } rational get_upper() const override { return m_upper; } - bool get_assignment(unsigned index) const override { return m_assignment[index]; } + bool get_assignment(unsigned index) const override { return m_soft[index].is_true; } void collect_statistics(statistics& st) const override { } void get_model(model_ref& mdl, svector& labels) override { mdl = m_model.get(); labels = m_labels;} virtual void commit_assignment(); @@ -153,6 +162,48 @@ namespace opt { solver& s(); }; + /** + \brief Standalone MaxSMT solver. + + It takes as input a solver object and provides a MaxSAT solver routine. + + It assumes the solver state is satisfiable and therefore there is a model + associated with the constraints asserted to the solver. A model of the + solver state must be supplied as a last argument. + + It assumes that the caller manages scope on the solver such that + the solver can be left in a stronger or inconsistent state upon return. + Callers should therefore use this feature under a push/pop. + */ + class maxsmt_wrapper { + params_ref m_params; + ref m_solver; + model_ref m_model; + public: + maxsmt_wrapper(params_ref & p, solver* s, model* m): + m_params(p), + m_solver(s), + m_model(m) {} + + lbool operator()(expr_ref_vector& soft) { + vector> _soft; + for (expr* e : soft) _soft.push_back(std::make_pair(e, rational::one())); + lbool r = (*this)(_soft); + soft.reset(); + for (auto const& p : _soft) soft.push_back(p.first); + return r; + } + + /** + \brief takes a vector of weighted soft constraints. + Returns a modified list of soft constraints that are + satisfied in the maximal satisfying assignment. + */ + lbool operator()(vector> & soft); + + model_ref get_model() { return m_model; } + }; + }; #endif diff --git a/src/opt/opt_context.cpp b/src/opt/opt_context.cpp index 2850dd59c..566aaa1f6 100644 --- a/src/opt/opt_context.cpp +++ b/src/opt/opt_context.cpp @@ -172,7 +172,7 @@ namespace opt { r.append(m_labels); } - void context::get_unsat_core(ptr_vector & r) { + void context::get_unsat_core(expr_ref_vector & r) { throw default_exception("Unsat cores are not supported with optimization"); } @@ -341,6 +341,7 @@ namespace opt { void context::fix_model(model_ref& mdl) { if (mdl && !m_model_fixed.contains(mdl.get())) { + TRACE("opt", tout << "fix-model\n";); (*m_fm)(mdl); apply(m_model_converter, mdl); m_model_fixed.push_back(mdl.get()); @@ -350,7 +351,8 @@ namespace opt { void context::get_model_core(model_ref& mdl) { mdl = m_model; fix_model(mdl); - TRACE("opt", model_smt2_pp(tout, m, *mdl.get(), 0);); + mdl->set_model_completion(true); + TRACE("opt", tout << *mdl;); } void context::get_box_model(model_ref& mdl, unsigned index) { @@ -502,7 +504,8 @@ namespace opt { case O_MINIMIZE: is_ge = !is_ge; case O_MAXIMIZE: - if (mdl->eval(obj.m_term, val, true) && is_numeral(val, k)) { + val = (*mdl)(obj.m_term); + if (is_numeral(val, k)) { if (is_ge) { result = mk_ge(obj.m_term, val); } @@ -522,11 +525,11 @@ namespace opt { for (unsigned i = 0; i < sz; ++i) { terms.push_back(obj.m_terms[i]); coeffs.push_back(obj.m_weights[i]); - if (mdl->eval(obj.m_terms[i], val, true) && m.is_true(val)) { + if (mdl->is_true(obj.m_terms[i])) { k += obj.m_weights[i]; } else { - TRACE("opt", tout << val << "\n";); + TRACE("opt", tout << (*mdl)(obj.m_terms[i]) << "\n";); } } if (is_ge) { @@ -1036,7 +1039,7 @@ namespace opt { buffer << prefix << (m_model_counter++) << ".smt2"; std::ofstream out(buffer.str()); if (out) { - model_smt2_pp(out, m, *mdl, 0); + out << *mdl; out.close(); } } @@ -1052,11 +1055,7 @@ namespace opt { expr_ref val(m); model_ref mdl = md->copy(); fix_model(mdl); - - if (!mdl->eval(term, val, true)) { - TRACE("opt", tout << "Term does not evaluate " << term << "\n";); - return false; - } + val = (*mdl)(term); unsigned bvsz; if (!m_arith.is_numeral(val, r) && !m_bv.is_numeral(val, r, bvsz)) { TRACE("opt", tout << "model does not evaluate objective to a value\n";); @@ -1195,9 +1194,9 @@ namespace opt { rational r; switch(obj.m_type) { case O_MINIMIZE: { - bool evaluated = m_model->eval(obj.m_term, val, true); - TRACE("opt", tout << obj.m_term << " " << val << " " << evaluated << " " << is_numeral(val, r) << "\n";); - if (evaluated && is_numeral(val, r)) { + val = (*m_model)(obj.m_term); + TRACE("opt", tout << obj.m_term << " " << val << " " << is_numeral(val, r) << "\n";); + if (is_numeral(val, r)) { inf_eps val = inf_eps(obj.m_adjust_value(r)); TRACE("opt", tout << "adjusted value: " << val << "\n";); if (is_lower) { @@ -1210,9 +1209,9 @@ namespace opt { break; } case O_MAXIMIZE: { - bool evaluated = m_model->eval(obj.m_term, val, true); + val = (*m_model)(obj.m_term); TRACE("opt", tout << obj.m_term << " " << val << "\n";); - if (evaluated && is_numeral(val, r)) { + if (is_numeral(val, r)) { inf_eps val = inf_eps(obj.m_adjust_value(r)); TRACE("opt", tout << "adjusted value: " << val << "\n";); if (is_lower) { @@ -1227,15 +1226,10 @@ namespace opt { case O_MAXSMT: { bool ok = true; for (unsigned j = 0; ok && j < obj.m_terms.size(); ++j) { - bool evaluated = m_model->eval(obj.m_terms[j], val, true); + val = (*m_model)(obj.m_terms[j]); TRACE("opt", tout << mk_pp(obj.m_terms[j], m) << " " << val << "\n";); - if (evaluated) { - if (!m.is_true(val)) { - r += obj.m_weights[j]; - } - } - else { - ok = false; + if (!m.is_true(val)) { + r += obj.m_weights[j]; } } if (ok) { @@ -1485,7 +1479,7 @@ namespace opt { } if (is_internal && mc) { - mc->collect(visitor); + mc->set_env(&visitor); } param_descrs descrs; @@ -1531,7 +1525,9 @@ namespace opt { if (is_internal && mc) { mc->display(out); } - + if (is_internal && mc) { + mc->set_env(nullptr); + } out << "(check-sat)\n"; return out.str(); } @@ -1544,8 +1540,9 @@ namespace opt { expr_ref tmp(m); model_ref mdl; get_model(mdl); + mdl->set_model_completion(true); for (expr * f : fmls) { - if (!mdl->eval(f, tmp) || !m.is_true(tmp)) { + if (!mdl->is_true(f)) { //IF_VERBOSE(0, m_fm->display(verbose_stream() << "fm\n")); IF_VERBOSE(0, m_model_converter->display(verbose_stream() << "mc\n")); IF_VERBOSE(0, verbose_stream() << "Failed to validate " << mk_pp(f, m) << "\n" << tmp << "\n"); @@ -1559,18 +1556,14 @@ namespace opt { void context::validate_maxsat(symbol const& id) { maxsmt& ms = *m_maxsmts.find(id); TRACE("opt", tout << "Validate: " << id << "\n";); - for (unsigned i = 0; i < m_objectives.size(); ++i) { - objective const& obj = m_objectives[i]; + for (objective const& obj : m_objectives) { if (obj.m_id == id && obj.m_type == O_MAXSMT) { SASSERT(obj.m_type == O_MAXSMT); rational value(0); expr_ref val(m); for (unsigned i = 0; i < obj.m_terms.size(); ++i) { - bool evaluated = m_model->eval(obj.m_terms[i], val); - SASSERT(evaluated); - CTRACE("opt", evaluated && !m.is_true(val) && !m.is_false(val), tout << mk_pp(obj.m_terms[i], m) << " " << val << "\n";); - CTRACE("opt", !evaluated, tout << mk_pp(obj.m_terms[i], m) << "\n";); - if (evaluated && !m.is_true(val)) { + auto const& t = obj.m_terms[i]; + if (!m_model->is_true(t)) { value += obj.m_weights[i]; } // TBD: check that optimal was not changed. @@ -1595,14 +1588,13 @@ namespace opt { if (m_optsmt.objective_is_model_valid(obj.m_index) && n.get_infinity().is_zero() && n.get_infinitesimal().is_zero() && - m_model->eval(obj.m_term, val) && - is_numeral(val, r1)) { + is_numeral((*m_model)(obj.m_term), r1)) { rational r2 = n.get_rational(); if (obj.m_type == O_MINIMIZE) { r1.neg(); } CTRACE("opt", r1 != r2, tout << obj.m_term << " evaluates to " << r1 << " but has objective " << r2 << "\n";); - CTRACE("opt", r1 != r2, model_smt2_pp(tout, m, *m_model, 0);); + CTRACE("opt", r1 != r2, tout << *m_model;); SASSERT(r1 == r2); } break; @@ -1610,8 +1602,7 @@ namespace opt { case O_MAXSMT: { rational value(0); for (unsigned i = 0; i < obj.m_terms.size(); ++i) { - bool evaluated = m_model->eval(obj.m_terms[i], val); - if (evaluated && !m.is_true(val)) { + if (!m_model->is_true(obj.m_terms[i])) { value += obj.m_weights[i]; } // TBD: check that optimal was not changed. diff --git a/src/opt/opt_context.h b/src/opt/opt_context.h index 29b327855..1172e68b6 100644 --- a/src/opt/opt_context.h +++ b/src/opt/opt_context.h @@ -195,7 +195,7 @@ namespace opt { void collect_statistics(statistics& stats) const override; proof* get_proof() override { return nullptr; } void get_labels(svector & r) override; - void get_unsat_core(ptr_vector & r) override; + void get_unsat_core(expr_ref_vector & r) override; std::string reason_unknown() const override; void set_reason_unknown(char const* msg) override { m_unknown = msg; } diff --git a/src/opt/opt_pareto.cpp b/src/opt/opt_pareto.cpp index 56eed72ac..fe92c3ba6 100644 --- a/src/opt/opt_pareto.cpp +++ b/src/opt/opt_pareto.cpp @@ -40,6 +40,7 @@ namespace opt { } m_solver->get_model(m_model); m_solver->get_labels(m_labels); + m_model->set_model_completion(true); IF_VERBOSE(1, model_ref mdl(m_model); cb.fix_model(mdl); @@ -99,6 +100,7 @@ namespace opt { if (is_sat == l_true) { m_solver->get_model(m_model); m_solver->get_labels(m_labels); + m_model->set_model_completion(true); mk_not_dominated_by(); } return is_sat; diff --git a/src/opt/opt_solver.cpp b/src/opt/opt_solver.cpp index 1a6587585..ca0474314 100644 --- a/src/opt/opt_solver.cpp +++ b/src/opt/opt_solver.cpp @@ -294,7 +294,8 @@ namespace opt { return r; } - void opt_solver::get_unsat_core(ptr_vector & r) { + void opt_solver::get_unsat_core(expr_ref_vector & r) { + r.reset(); unsigned sz = m_context.get_unsat_core_size(); for (unsigned i = 0; i < sz; i++) { r.push_back(m_context.get_unsat_core_expr(i)); diff --git a/src/opt/opt_solver.h b/src/opt/opt_solver.h index 7ffb531be..39562ec54 100644 --- a/src/opt/opt_solver.h +++ b/src/opt/opt_solver.h @@ -96,7 +96,7 @@ namespace opt { void push_core() override; void pop_core(unsigned n) override; lbool check_sat_core(unsigned num_assumptions, expr * const * assumptions) override; - void get_unsat_core(ptr_vector & r) override; + void get_unsat_core(expr_ref_vector & r) override; void get_model_core(model_ref & _m) override; proof * get_proof() override; std::string reason_unknown() const override; diff --git a/src/opt/optsmt.cpp b/src/opt/optsmt.cpp index 702702ef4..a461d4a22 100644 --- a/src/opt/optsmt.cpp +++ b/src/opt/optsmt.cpp @@ -318,7 +318,7 @@ namespace opt { m_s->get_labels(m_labels); for (unsigned i = 0; i < ors.size(); ++i) { expr_ref tmp(m); - if (m_model->eval(ors[i].get(), tmp) && m.is_true(tmp)) { + if (m_model->is_true(ors[i].get())) { m_lower[i] = m_upper[i]; ors[i] = m.mk_false(); disj[i] = m.mk_false(); diff --git a/src/opt/pb_sls.cpp b/src/opt/pb_sls.cpp index e28c3cd3d..9054e2b00 100644 --- a/src/opt/pb_sls.cpp +++ b/src/opt/pb_sls.cpp @@ -179,7 +179,7 @@ namespace smt { m_orig_model = mdl; for (unsigned i = 0; i < m_var2decl.size(); ++i) { expr_ref tmp(m); - m_assignment[i] = mdl->eval(m_var2decl[i], tmp) && m.is_true(tmp); + m_assignment[i] = mdl->is_true(m_var2decl[i]); } } @@ -343,10 +343,7 @@ namespace smt { for (unsigned i = 0; i < m_clauses.size(); ++i) { if (!eval(m_clauses[i])) { m_hard_false.insert(i); - expr_ref tmp(m); - if (!m_orig_model->eval(m_orig_clauses[i].get(), tmp)) { - return; - } + expr_ref tmp = (*m_orig_model)(m_orig_clauses[i].get()); IF_VERBOSE(0, verbose_stream() << "original evaluation: " << tmp << "\n"; verbose_stream() << mk_pp(m_orig_clauses[i].get(), m) << "\n"; @@ -521,14 +518,13 @@ namespace smt { literal mk_aux_literal(expr* f) { unsigned var; - expr_ref tmp(m); if (!m_decl2var.find(f, var)) { var = m_hard_occ.size(); SASSERT(m_var2decl.size() == var); SASSERT(m_soft_occ.size() == var); m_hard_occ.push_back(unsigned_vector()); m_soft_occ.push_back(unsigned_vector()); - m_assignment.push_back(m_orig_model->eval(f, tmp) && m.is_true(tmp)); + m_assignment.push_back(m_orig_model->is_true(f)); m_decl2var.insert(f, var); m_var2decl.push_back(f); } diff --git a/src/opt/sortmax.cpp b/src/opt/sortmax.cpp index 9c45e42a2..4313cfbec 100644 --- a/src/opt/sortmax.cpp +++ b/src/opt/sortmax.cpp @@ -73,8 +73,7 @@ namespace opt { unsigned first = 0; it = soft.begin(); for (; it != end; ++it) { - expr_ref tmp(m); - if (m_model->eval(it->m_key, tmp) && m.is_true(tmp)) { + if (m_model->is_true(it->m_key)) { unsigned n = it->m_value.get_unsigned(); while (n > 0) { s().assert_expr(out[first]); @@ -115,14 +114,11 @@ namespace opt { } void update_assignment() { - for (unsigned i = 0; i < m_soft.size(); ++i) { - m_assignment[i] = is_true(m_soft[i]); - } + for (soft& s : m_soft) s.is_true = is_true(s.s); } bool is_true(expr* e) { - expr_ref tmp(m); - return m_model->eval(e, tmp) && m.is_true(tmp); + return m_model->is_true(e); } // definitions used for sorting network diff --git a/src/opt/wmax.cpp b/src/opt/wmax.cpp index 513c68d6e..e4eb7e06b 100644 --- a/src/opt/wmax.cpp +++ b/src/opt/wmax.cpp @@ -120,15 +120,11 @@ namespace opt { } bool is_true(expr* e) { - expr_ref tmp(m); - return m_model->eval(e, tmp) && m.is_true(tmp); + return m_model->is_true(e); } void update_assignment() { - m_assignment.reset(); - for (unsigned i = 0; i < m_soft.size(); ++i) { - m_assignment.push_back(is_true(m_soft[i])); - } + for (soft& s : m_soft) s.is_true = is_true(s.s); } struct compare_asm { @@ -307,9 +303,8 @@ namespace opt { } void update_model(expr* def, expr* value) { - expr_ref val(m); - if (m_model && m_model->eval(value, val, true)) { - m_model->register_decl(to_app(def)->get_decl(), val); + if (m_model) { + m_model->register_decl(to_app(def)->get_decl(), (*m_model)(value)); } } diff --git a/src/parsers/smt2/smt2parser.cpp b/src/parsers/smt2/smt2parser.cpp index 9276b60cd..016c6f4e5 100644 --- a/src/parsers/smt2/smt2parser.cpp +++ b/src/parsers/smt2/smt2parser.cpp @@ -2611,8 +2611,8 @@ namespace smt2 { expr ** expr_it = expr_stack().c_ptr() + spos; expr ** expr_end = expr_it + m_cached_strings.size(); for (unsigned i = 0; expr_it < expr_end; expr_it++, i++) { - expr_ref v(m()); - md->eval(*expr_it, v, true); + model::scoped_model_completion _scm(md, true); + expr_ref v = (*md)(*expr_it); if (i > 0) m_ctx.regular_stream() << "\n "; m_ctx.regular_stream() << "(" << m_cached_strings[i] << " "; diff --git a/src/parsers/util/cost_parser.cpp b/src/parsers/util/cost_parser.cpp index 765b8ade9..91362b37a 100644 --- a/src/parsers/util/cost_parser.cpp +++ b/src/parsers/util/cost_parser.cpp @@ -32,7 +32,7 @@ cost_parser::cost_parser(ast_manager & m): add_builtin_op("or", fid, OP_OR); add_builtin_op("ite", fid, OP_ITE); add_builtin_op("=", fid, OP_EQ); - add_builtin_op("iff", fid, OP_IFF); + add_builtin_op("iff", fid, OP_EQ); add_builtin_op("xor", fid, OP_XOR); fid = m_util.get_family_id(); diff --git a/src/qe/CMakeLists.txt b/src/qe/CMakeLists.txt index 2e6052382..e9f91ae3e 100644 --- a/src/qe/CMakeLists.txt +++ b/src/qe/CMakeLists.txt @@ -15,8 +15,11 @@ z3_add_component(qe qe_dl_plugin.cpp qe_lite.cpp qe_mbp.cpp + qe_mbi.cpp qe_sat_tactic.cpp + qe_solve_plugin.cpp qe_tactic.cpp + qe_term_graph.cpp qsat.cpp COMPONENT_DEPENDENCIES nlsat_tactic diff --git a/src/qe/nlqsat.cpp b/src/qe/nlqsat.cpp index d8d6705fe..1ae9723c9 100644 --- a/src/qe/nlqsat.cpp +++ b/src/qe/nlqsat.cpp @@ -205,8 +205,7 @@ namespace qe { nlsat::scoped_literal_vector new_result(m_solver); result.reset(); // project quantified Boolean variables. - for (unsigned i = 0; i < m_asms.size(); ++i) { - nlsat::literal lit = m_asms[i]; + for (nlsat::literal lit : m_asms) { if (!m_b2a.contains(lit.var()) || fvars.contains(lit.var())) { result.push_back(lit); } @@ -215,12 +214,13 @@ namespace qe { // project quantified real variables. // They are sorted by size, so we project the largest variables first to avoid // renaming variables. - for (unsigned i = vars.size(); i > 0;) { - --i; + for (unsigned i = vars.size(); i-- > 0;) { new_result.reset(); + TRACE("qe", m_solver.display(tout << "project: ", vars[i]) << "\n";); ex.project(vars[i], result.size(), result.c_ptr(), new_result); result.swap(new_result); - TRACE("qe", m_solver.display(tout, result.size(), result.c_ptr()); tout << "\n";); + TRACE("qe", m_solver.display(tout, vars[i]) << ": "; + m_solver.display(tout, result.size(), result.c_ptr()); tout << "\n";); } negate_clause(result); } @@ -596,6 +596,7 @@ namespace qe { } void display(std::ostream& out) { + out << "level " << level() << "\n"; display_preds(out); display_assumptions(out); m_solver.display(out << "solver:\n"); @@ -645,12 +646,8 @@ namespace qe { while (!vars.empty()); SASSERT(qvars.size() >= 2); SASSERT(qvars.back().empty()); - ackermanize_div(is_forall, qvars, fml); - init_expr2var(qvars); - - goal2nlsat g2s; expr_ref is_true(m), fml1(m), fml2(m); @@ -671,9 +668,7 @@ namespace qe { m_bound_rvars.push_back(nlsat::var_vector()); max_level lvl; if (is_exists(i)) lvl.m_ex = i; else lvl.m_fa = i; - for (unsigned j = 0; j < qvars[i].size(); ++j) { - app* v = qvars[i][j].get(); - + for (app* v : qvars[i]) { if (m_a2b.is_var(v)) { SASSERT(m.is_bool(v)); nlsat::bool_var b = m_a2b.to_var(v); @@ -682,7 +677,7 @@ namespace qe { } else if (m_t2x.is_var(v)) { nlsat::var w = m_t2x.to_var(v); - TRACE("qe", tout << mk_pp(v, m) << " |-> " << w << "\n";); + TRACE("qe", tout << mk_pp(v, m) << " |-> x" << w << "\n";); m_bound_rvars.back().push_back(w); m_rvar2level.setx(w, lvl, max_level()); } @@ -695,7 +690,7 @@ namespace qe { m_is_true = nlsat::literal(m_a2b.to_var(is_true), false); // insert literals from arithmetical sub-formulas nlsat::atom_vector const& atoms = m_solver.get_atoms(); - TRACE("qe", m_solver.display(tout); ); + TRACE("qe", m_solver.display(tout);); for (unsigned i = 0; i < atoms.size(); ++i) { if (atoms[i]) { get_level(nlsat::literal(i, false)); @@ -724,13 +719,11 @@ namespace qe { } void init_var2expr() { - expr2var::iterator it = m_t2x.begin(), end = m_t2x.end(); - for (; it != end; ++it) { - m_x2t.insert(it->m_value, it->m_key); + for (auto const& kv : m_t2x) { + m_x2t.insert(kv.m_value, kv.m_key); } - it = m_a2b.begin(), end = m_a2b.end(); - for (; it != end; ++it) { - m_b2a.insert(it->m_value, it->m_key); + for (auto const& kv : m_a2b) { + m_b2a.insert(kv.m_value, kv.m_key); } } @@ -741,10 +734,9 @@ namespace qe { bool ok = true; model_ref md = alloc(model, m); arith_util util(m); - expr2var::iterator it = m_t2x.begin(), end = m_t2x.end(); - for (; it != end; ++it) { - nlsat::var x = it->m_value; - expr * t = it->m_key; + for (auto const& kv : m_t2x) { + nlsat::var x = kv.m_value; + expr * t = kv.m_key; if (!is_uninterp_const(t) || !m_free_vars.contains(t) || m_aux_vars.contains(t)) continue; expr * v; @@ -760,10 +752,9 @@ namespace qe { } md->register_decl(to_app(t)->get_decl(), v); } - it = m_a2b.begin(), end = m_a2b.end(); - for (; it != end; ++it) { - expr * a = it->m_key; - nlsat::bool_var b = it->m_value; + for (auto const& kv : m_a2b) { + expr * a = kv.m_key; + nlsat::bool_var b = kv.m_value; if (a == nullptr || !is_uninterp_const(a) || b == m_is_true.var() || !m_free_vars.contains(a) || m_aux_vars.contains(a)) continue; lbool val = m_bmodel0.get(b, l_undef); diff --git a/src/qe/qe.cpp b/src/qe/qe.cpp index daed18f7a..3916e547c 100644 --- a/src/qe/qe.cpp +++ b/src/qe/qe.cpp @@ -614,7 +614,7 @@ namespace qe { else if (m.is_ite(a)) { nnf_ite(a, p); } - else if (m.is_iff(a) || (m.is_eq(a) && m.is_bool(a->get_arg(0)))) { + else if (m.is_iff(a)) { nnf_iff(a, p); } else if (m.is_xor(a)) { diff --git a/src/qe/qe_arith.cpp b/src/qe/qe_arith.cpp index 6ea4118a0..ff4c7dc52 100644 --- a/src/qe/qe_arith.cpp +++ b/src/qe/qe_arith.cpp @@ -30,6 +30,7 @@ Revision History: #include "ast/rewriter/expr_safe_replace.h" #include "math/simplex/model_based_opt.h" #include "model/model_evaluator.h" +#include "model/model_smt2_pp.h" namespace qe { @@ -37,6 +38,7 @@ namespace qe { ast_manager& m; arith_util a; + bool m_check_purified; // check that variables are properly pure void insert_mul(expr* x, rational const& v, obj_map& ts) { TRACE("qe", tout << "Adding variable " << mk_pp(x, m) << " " << v << "\n";); @@ -90,6 +92,8 @@ namespace qe { rational r1, r2; expr_ref val1 = eval(e1); expr_ref val2 = eval(e2); + TRACE("qe", tout << mk_pp(e1, m) << " " << val1 << "\n";); + TRACE("qe", tout << mk_pp(e2, m) << " " << val2 << "\n";); if (!a.is_numeral(val1, r1)) return false; if (!a.is_numeral(val2, r2)) return false; SASSERT(r1 != r2); @@ -107,6 +111,7 @@ namespace qe { vector > nums; for (expr* arg : *alit) { val = eval(arg); + TRACE("qe", tout << mk_pp(arg, m) << " " << val << "\n";); if (!a.is_numeral(val, r)) return false; nums.push_back(std::make_pair(arg, r)); } @@ -129,6 +134,7 @@ namespace qe { expr* arg1 = to_app(lit)->get_arg(i), *arg2 = nullptr; rational r; expr_ref val = eval(arg1); + TRACE("qe", tout << mk_pp(arg1, m) << " " << val << "\n";); if (!a.is_numeral(val, r)) return false; if (values.find(r, arg2)) { ty = opt::t_eq; @@ -256,7 +262,7 @@ namespace qe { }; bool is_arith(expr* e) { - return a.is_int(e) || a.is_real(e); + return a.is_int_real(e); } rational n_sign(rational const& b) { @@ -264,7 +270,7 @@ namespace qe { } imp(ast_manager& m): - m(m), a(m) {} + m(m), a(m), m_check_purified(true) {} ~imp() {} @@ -275,7 +281,7 @@ namespace qe { bool operator()(model& model, app* v, app_ref_vector& vars, expr_ref_vector& lits) { app_ref_vector vs(m); vs.push_back(v); - (*this)(model, vs, lits); + project(model, vs, lits, false); return vs.empty(); } @@ -283,36 +289,43 @@ namespace qe { typedef opt::model_based_opt::row row; typedef vector vars; - void operator()(model& model, app_ref_vector& vars, expr_ref_vector& fmls) { + expr_ref var2expr(ptr_vector const& index2expr, var const& v) { + expr_ref t(index2expr[v.m_id], m); + if (!v.m_coeff.is_one()) { + t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t); + } + return t; + } + + vector project(model& model, app_ref_vector& vars, expr_ref_vector& fmls, bool compute_def) { bool has_arith = false; - for (unsigned i = 0; !has_arith && i < vars.size(); ++i) { - expr* v = vars[i].get(); + for (expr* v : vars) { has_arith |= is_arith(v); } if (!has_arith) { - return; + return vector(); } model_evaluator eval(model); + TRACE("qe", model_smt2_pp(tout, m, model, 0);); // eval.set_model_completion(true); opt::model_based_opt mbo; obj_map tids; expr_ref_vector pinned(m); unsigned j = 0; + TRACE("qe", tout << "fmls: " << fmls << "\n";); for (unsigned i = 0; i < fmls.size(); ++i) { - expr* fml = fmls[i].get(); + expr * fml = fmls.get(i); if (!linearize(mbo, eval, fml, fmls, tids)) { - if (i != j) { - fmls[j] = fmls[i].get(); - } - ++j; + TRACE("qe", tout << "could not linearize: " << mk_pp(fml, m) << "\n";); + fmls[j++] = fml; } else { - TRACE("qe", tout << mk_pp(fml, m) << "\n";); pinned.push_back(fml); } } - fmls.resize(j); + fmls.shrink(j); + TRACE("qe", tout << "linearized: " << fmls << "\n";); // fmls holds residue, // mbo holds linear inequalities that are in scope @@ -328,51 +341,53 @@ namespace qe { if (is_arith(v) && !tids.contains(v)) { rational r; expr_ref val = eval(v); - a.is_numeral(val, r); + VERIFY(a.is_numeral(val, r)); TRACE("qe", tout << mk_pp(v, m) << " " << val << "\n";); tids.insert(v, mbo.add_var(r, a.is_int(v))); } } - for (expr* fml : fmls) { - fmls_mark.mark(fml); + if (m_check_purified) { + for (expr* fml : fmls) { + mark_rec(fmls_mark, fml); + } + for (auto& kv : tids) { + expr* e = kv.m_key; + if (!var_mark.is_marked(e)) { + mark_rec(fmls_mark, e); + } + } } + ptr_vector index2expr; for (auto& kv : tids) { - expr* e = kv.m_key; - if (!var_mark.is_marked(e)) { - mark_rec(fmls_mark, e); - } - index2expr.setx(kv.m_value, e, 0); + index2expr.setx(kv.m_value, kv.m_key, nullptr); } + j = 0; unsigned_vector real_vars; - for (unsigned i = 0; i < vars.size(); ++i) { - app* v = vars[i].get(); + for (app* v : vars) { if (is_arith(v) && !fmls_mark.is_marked(v)) { real_vars.push_back(tids.find(v)); } else { - if (i != j) { - vars[j] = v; - } - ++j; + vars[j++] = v; } } - vars.resize(j); + vars.shrink(j); + TRACE("qe", tout << "remaining vars: " << vars << "\n"; for (unsigned v : real_vars) { tout << "v" << v << " " << mk_pp(index2expr[v], m) << "\n"; } mbo.display(tout);); - mbo.project(real_vars.size(), real_vars.c_ptr()); + vector defs = mbo.project(real_vars.size(), real_vars.c_ptr(), compute_def); TRACE("qe", mbo.display(tout);); vector rows; mbo.get_live_rows(rows); - for (unsigned i = 0; i < rows.size(); ++i) { + for (row const& r : rows) { expr_ref_vector ts(m); expr_ref t(m), s(m), val(m); - row const& r = rows[i]; if (r.m_vars.size() == 0) { continue; } @@ -394,41 +409,87 @@ namespace qe { CTRACE("qe", !m.is_true(val), tout << "Evaluated unit " << t << " to " << val << "\n";); continue; } - for (j = 0; j < r.m_vars.size(); ++j) { - var const& v = r.m_vars[j]; + for (var const& v : r.m_vars) { t = index2expr[v.m_id]; if (!v.m_coeff.is_one()) { t = a.mk_mul(a.mk_numeral(v.m_coeff, a.is_int(t)), t); } ts.push_back(t); } - s = a.mk_numeral(-r.m_coeff, a.is_int(t)); - if (ts.size() == 1) { - t = ts[0].get(); - } - else { - t = a.mk_add(ts.size(), ts.c_ptr()); - } + t = mk_add(ts); + s = a.mk_numeral(-r.m_coeff, r.m_coeff.is_int() && a.is_int(t)); switch (r.m_type) { case opt::t_lt: t = a.mk_lt(t, s); break; case opt::t_le: t = a.mk_le(t, s); break; case opt::t_eq: t = a.mk_eq(t, s); break; - case opt::t_mod: { + case opt::t_mod: if (!r.m_coeff.is_zero()) { t = a.mk_sub(t, s); } t = a.mk_eq(a.mk_mod(t, a.mk_numeral(r.m_mod, true)), a.mk_int(0)); break; } - } fmls.push_back(t); - val = eval(t); CTRACE("qe", !m.is_true(val), tout << "Evaluated " << t << " to " << val << "\n";); - } + vector result; + if (compute_def) { + SASSERT(defs.size() == real_vars.size()); + for (unsigned i = 0; i < defs.size(); ++i) { + auto const& d = defs[i]; + expr* x = index2expr[real_vars[i]]; + bool is_int = a.is_int(x); + expr_ref_vector ts(m); + expr_ref t(m); + for (var const& v : d.m_vars) { + ts.push_back(var2expr(index2expr, v)); + } + if (!d.m_coeff.is_zero()) + ts.push_back(a.mk_numeral(d.m_coeff, is_int)); + t = mk_add(ts); + if (!d.m_div.is_one() && is_int) { + t = a.mk_idiv(t, a.mk_numeral(d.m_div, is_int)); + } + else if (!d.m_div.is_one() && !is_int) { + t = a.mk_div(t, a.mk_numeral(d.m_div, is_int)); + } + update_model(model, to_app(x), eval(t)); + + SASSERT(eval(t) == eval(x)); + result.push_back(def(expr_ref(x, m), t)); + } + } + return result; } + void update_model(model& mdl, app* x, expr_ref const& val) { + if (is_uninterp_const(x)) { + mdl.register_decl(x->get_decl(), val); + } + else { + func_interp* fi = mdl.get_func_interp(x->get_decl()); + if (!fi) return; + model_evaluator eval(mdl); + expr_ref_vector args(m); + for (expr* arg : *x) { + args.push_back(eval(arg)); + } + fi->insert_entry(args.c_ptr(), val); + } + } + + expr_ref mk_add(expr_ref_vector const& ts) { + switch (ts.size()) { + case 0: + return expr_ref(a.mk_int(0), m); + case 1: + return expr_ref(ts.get(0), m); + default: + return expr_ref(a.mk_add(ts.size(), ts.c_ptr()), m); + } + } + opt::inf_eps maximize(expr_ref_vector const& fmls0, model& mdl, app* t, expr_ref& ge, expr_ref& gt) { SASSERT(a.is_real(t)); expr_ref_vector fmls(fmls0); @@ -539,7 +600,15 @@ namespace qe { } void arith_project_plugin::operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { - (*m_imp)(model, vars, lits); + m_imp->project(model, vars, lits, false); + } + + vector arith_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { + return m_imp->project(model, vars, lits, true); + } + + void arith_project_plugin::set_check_purified(bool check_purified) { + m_imp->m_check_purified = check_purified; } bool arith_project_plugin::solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) { diff --git a/src/qe/qe_arith.h b/src/qe/qe_arith.h index 8e2400ac7..b55e63fcf 100644 --- a/src/qe/qe_arith.h +++ b/src/qe/qe_arith.h @@ -30,9 +30,15 @@ namespace qe { bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; family_id get_family_id() override; void operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; - + vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; opt::inf_eps maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt); + + /** + * \brief check if formulas are purified, or leave it to caller to ensure that + * arithmetic variables nested under foreign functions are handled properly. + */ + void set_check_purified(bool check_purified); }; bool arith_project(model& model, app* var, expr_ref_vector& lits); diff --git a/src/qe/qe_arrays.cpp b/src/qe/qe_arrays.cpp index a227d755e..4c81418b6 100644 --- a/src/qe/qe_arrays.cpp +++ b/src/qe/qe_arrays.cpp @@ -18,95 +18,1179 @@ Revision History: --*/ -#include "qe/qe_arrays.h" +#include "util/lbool.h" #include "ast/rewriter/rewriter_def.h" #include "ast/expr_functors.h" #include "ast/rewriter/expr_safe_replace.h" -#include "util/lbool.h" +#include "ast/rewriter/th_rewriter.h" #include "ast/ast_util.h" #include "ast/ast_pp.h" +#include "model/model_evaluator.h" +#include "qe/qe_arrays.h" + + +namespace { + bool is_partial_eq (app* a); + + /** + * \brief utility class for partial equalities + * + * A partial equality (a ==I b), for two arrays a,b and a finite set of indices I holds + * iff (Forall i. i \not\in I => a[i] == b[i]); in other words, it is a + * restricted form of the extensionality axiom + * + * using this class, we denote (a =I b) as f(a,b,i0,i1,...) + * where f is an uninterpreted predicate with name PARTIAL_EQ and + * I = {i0,i1,...} + */ + + // TBD: make work for arrays with multiple arguments. + class peq { + ast_manager& m; + expr_ref m_lhs; + expr_ref m_rhs; + vector m_diff_indices; + func_decl_ref m_decl; // the partial equality declaration + app_ref m_peq; // partial equality application + app_ref m_eq; // equivalent std equality using def. of partial eq + array_util m_arr_u; + + public: + static const char* PARTIAL_EQ; + + peq (app* p, ast_manager& m): + m (m), + m_lhs (p->get_arg (0), m), + m_rhs (p->get_arg (1), m), + m_decl (p->get_decl (), m), + m_peq (p, m), + m_eq (m), + m_arr_u (m) + { + VERIFY (is_partial_eq (p)); + SASSERT (m_arr_u.is_array (m_lhs) && + m_arr_u.is_array (m_rhs) && + m.get_sort(m_lhs) == m.get_sort(m_rhs)); + unsigned arity = get_array_arity(m.get_sort(m_lhs)); + for (unsigned i = 2; i < p->get_num_args (); i += arity) { + SASSERT(arity + i <= p->get_num_args()); + expr_ref_vector vec(m); + vec.append(arity, p->get_args() + i); + m_diff_indices.push_back (vec); + } + } + + peq (expr* lhs, expr* rhs, vector const& diff_indices, ast_manager& m): + m (m), + m_lhs (lhs, m), + m_rhs (rhs, m), + m_diff_indices (diff_indices), + m_decl (m), + m_peq (m), + m_eq (m), + m_arr_u (m) { + SASSERT (m_arr_u.is_array (lhs) && + m_arr_u.is_array (rhs) && + m.get_sort(lhs) == m.get_sort(rhs)); + ptr_vector sorts; + sorts.push_back (m.get_sort (m_lhs)); + sorts.push_back (m.get_sort (m_rhs)); + for (auto const& v : diff_indices) { + SASSERT(v.size() == get_array_arity(m.get_sort(m_lhs))); + for (expr* e : v) + sorts.push_back (m.get_sort(e)); + } + m_decl = m.mk_func_decl (symbol (PARTIAL_EQ), sorts.size (), sorts.c_ptr (), m.mk_bool_sort ()); + } + + expr_ref lhs () { return m_lhs; } + + expr_ref rhs () { return m_rhs; } + + void get_diff_indices (vector& result) { result.append(m_diff_indices); } + + app_ref mk_peq () { + if (!m_peq) { + ptr_vector args; + args.push_back (m_lhs); + args.push_back (m_rhs); + for (auto const& v : m_diff_indices) { + args.append (v.size(), v.c_ptr()); + } + m_peq = m.mk_app (m_decl, args.size (), args.c_ptr ()); + } + return m_peq; + } + + app_ref mk_eq (app_ref_vector& aux_consts, bool stores_on_rhs = true) { + if (!m_eq) { + expr_ref lhs (m_lhs, m), rhs (m_rhs, m); + if (!stores_on_rhs) { + std::swap (lhs, rhs); + } + // lhs = (...(store (store rhs i0 v0) i1 v1)...) + sort* val_sort = get_array_range (m.get_sort (lhs)); + for (expr_ref_vector const& diff : m_diff_indices) { + ptr_vector store_args; + store_args.push_back (rhs); + store_args.append (diff.size(), diff.c_ptr()); + app_ref val(m.mk_fresh_const ("diff", val_sort), m); + store_args.push_back (val); + aux_consts.push_back (val); + rhs = m_arr_u.mk_store (store_args.size (), store_args.c_ptr ()); + } + m_eq = m.mk_eq (lhs, rhs); + } + return m_eq; + } + }; + + const char* peq::PARTIAL_EQ = "!partial_eq"; + + bool is_partial_eq (app* a) { + return a->get_decl ()->get_name () == peq::PARTIAL_EQ; + } +} namespace qe { - struct array_project_plugin::imp { - // rewriter or direct procedure. - struct rw_cfg : public default_rewriter_cfg { - ast_manager& m; - array_util& a; - expr_ref_vector m_lits; - model* m_model; - imp* m_imp; - - rw_cfg(ast_manager& m, array_util& a): - m(m), a(a), m_lits(m), m_model(nullptr) {} - - br_status reduce_app(func_decl* f, unsigned n, expr* const* args, expr_ref& result, proof_ref & pr) { - if (a.is_select(f) && a.is_store(args[0])) { - expr_ref val1(m), val2(m); - app* b = to_app(args[0]); - SASSERT(b->get_num_args() == n + 1); - for (unsigned i = 1; i < n; ++i) { - expr* arg1 = args[i]; - expr* arg2 = b->get_arg(i); - if (arg1 == arg2) { - val1 = val2 = arg1; + static bool is_eq(expr_ref_vector const& xs, expr_ref_vector const& ys) { + for (unsigned i = 0; i < xs.size(); ++i) if (xs[i] != ys[i]) return false; + return true; + } + + static expr_ref mk_eq(expr_ref_vector const& xs, expr_ref_vector const& ys) { + ast_manager& m = xs.get_manager(); + expr_ref_vector eqs(m); + for (unsigned i = 0; i < xs.size(); ++i) eqs.push_back(m.mk_eq(xs[i], ys[i])); + return mk_and(eqs); + } + + /** + * 1. Extract all equalities (store (store .. (store x i1 v1) i2 v2) .. ) = ... + * where x appears and the equalities do not evaluate to false in the current model. + * 2. Track them as partial equivalence relations. + * 3. Sort them according to nesting depth. + * 4. Solve for x by potentially introducing fresh variables. + * Thus, (store x i v) = y, then + * x = (store y i w), (select y i) = v, where w is fresh and evaluates to (select x i). + * Generally, equalities are tracked as x =_idxs y, where x, y are equal up to idxs. + */ + + class array_project_eqs_util { + ast_manager& m; + array_util m_arr_u; + model_ref M; + model_evaluator* m_mev; + app_ref m_v; // array var to eliminate + ast_mark m_has_stores_v; // has stores for m_v + expr_ref m_subst_term_v; // subst term for m_v + expr_safe_replace m_true_sub_v; // subst for true equalities + expr_safe_replace m_false_sub_v; // subst for false equalities + expr_ref_vector m_aux_lits_v; + expr_ref_vector m_idx_lits_v; + app_ref_vector m_aux_vars; + + void reset_v () { + m_v = nullptr; + m_has_stores_v.reset (); + m_subst_term_v = nullptr; + m_true_sub_v.reset (); + m_false_sub_v.reset (); + m_aux_lits_v.reset (); + m_idx_lits_v.reset (); + } + + void reset () { + M = nullptr; + m_mev = nullptr; + reset_v (); + m_aux_vars.reset (); + } + + /** + * find all array equalities on m_v or containing stores on/of m_v + * + * also mark terms containing stores on/of m_v + */ + void find_arr_eqs (expr_ref const& fml, app_ref_vector& eqs) { + if (!is_app (fml)) return; + ast_mark done; + ptr_vector todo; + todo.push_back (to_app (fml)); + while (!todo.empty ()) { + app* a = todo.back (); + if (done.is_marked (a)) { + todo.pop_back (); + continue; + } + bool all_done = true; + bool args_have_stores = false; + for (expr * arg : *a) { + if (!is_app (arg)) continue; + if (!done.is_marked (arg)) { + all_done = false; + todo.push_back (to_app (arg)); + } + else if (!args_have_stores && m_has_stores_v.is_marked (arg)) { + args_have_stores = true; + } + } + if (!all_done) continue; + todo.pop_back (); + + // mark if a has stores + if ((!m_arr_u.is_select (a) && args_have_stores) || + (m_arr_u.is_store (a) && (a->get_arg (0) == m_v))) { + m_has_stores_v.mark (a, true); + + TRACE ("qe", + tout << "has stores:\n"; + tout << mk_pp (a, m) << "\n"; + ); + } + + // check if a is a relevant array equality + expr * a0 = nullptr, *a1 = nullptr; + if (m.is_eq (a, a0, a1)) { + if (a0 == m_v || a1 == m_v || + (m_arr_u.is_array (a0) && m_has_stores_v.is_marked (a))) { + eqs.push_back (a); + } + } + // else, we can check for disequalities and handle them using extensionality, + // but it's not necessary + + done.mark (a, true); + } + } + + /** + * factor out select terms on m_v using fresh consts + */ + void factor_selects (app_ref& fml) { + expr_map sel_cache (m); + ast_mark done; + ptr_vector todo; + expr_ref_vector pinned (m); // to ensure a reference + + todo.push_back (fml); + while (!todo.empty ()) { + app* a = todo.back (); + if (done.is_marked (a)) { + todo.pop_back (); + continue; + } + expr_ref_vector args (m); + bool all_done = true; + for (expr * arg : *a) { + if (!is_app (arg)) { + args.push_back(arg); + } + else if (!done.is_marked (arg)) { + all_done = false; + todo.push_back (to_app (arg)); + } + else if (all_done) { // all done so far.. + expr* arg_new = nullptr; proof* pr; + sel_cache.get (arg, arg_new, pr); + if (!arg_new) { + arg_new = arg; } - else { - VERIFY(m_model->eval(arg1, val1)); - VERIFY(m_model->eval(arg2, val2)); - } - switch(compare(val1, val2)) { - case l_true: - if (arg1 != arg2) { - m_lits.push_back(m.mk_eq(arg1, arg2)); + args.push_back (arg_new); + } + } + if (!all_done) continue; + todo.pop_back (); + + expr_ref a_new (m.mk_app (a->get_decl (), args.size (), args.c_ptr ()), m); + + // if a_new is select on m_v, introduce new constant + if (m_arr_u.is_select (a) && + (args.get (0) == m_v || m_has_stores_v.is_marked (args.get (0)))) { + sort* val_sort = get_array_range (m.get_sort (m_v)); + app_ref val_const (m.mk_fresh_const ("sel", val_sort), m); + m_aux_vars.push_back (val_const); + // extend M to include val_const + expr_ref val = (*m_mev)(a_new); + M->register_decl (val_const->get_decl (), val); + // add equality + m_aux_lits_v.push_back (m.mk_eq (val_const, a_new)); + // replace select by const + a_new = val_const; + } + + if (a != a_new) { + sel_cache.insert (a, a_new, nullptr); + pinned.push_back (a_new); + } + done.mark (a, true); + } + expr* res = nullptr; proof* pr; + sel_cache.get (fml, res, pr); + if (res) { + fml = to_app (res); + } + } + + /** + * convert partial equality expression p_exp to an equality by + * recursively adding stores on diff indices + * + * add stores on lhs or rhs depending on whether stores_on_rhs is false/true + */ + void convert_peq_to_eq (expr* p_exp, app_ref& eq, bool stores_on_rhs = true) { + peq p (to_app (p_exp), m); + app_ref_vector diff_val_consts (m); + eq = p.mk_eq (diff_val_consts, stores_on_rhs); + m_aux_vars.append (diff_val_consts); + // extend M to include diff_val_consts + vector I; + expr_ref arr = p.lhs (); + p.get_diff_indices (I); + expr_ref val (m); + unsigned num_diff = diff_val_consts.size (); + SASSERT (num_diff == I.size ()); + for (unsigned i = 0; i < num_diff; i++) { + // mk val term + ptr_vector sel_args; + sel_args.push_back (arr); + sel_args.append(I[i].size(), I[i].c_ptr()); + expr_ref val_term (m_arr_u.mk_select (sel_args.size (), sel_args.c_ptr ()), m); + // evaluate and assign to ith diff_val_const + val = (*m_mev)(val_term); + M->register_decl (diff_val_consts.get (i)->get_decl (), val); + } + } + + /** + * mk (e0 ==indices e1) + * + * result has stores if either e0 or e1 or an index term has stores + */ + app_ref mk_peq (expr* e0, expr* e1, vector const& indices) { + peq p (e0, e1, indices, m); + return p.mk_peq (); + } + + void find_subst_term (app* eq) { + SASSERT(m.is_eq(eq)); + vector empty; + app_ref p_exp = mk_peq (eq->get_arg (0), eq->get_arg (1), empty); + bool subst_eq_found = false; + while (true) { + TRACE ("qe", tout << "processing peq:\n" << p_exp << "\n";); + + peq p (p_exp, m); + expr_ref lhs = p.lhs(), rhs = p.rhs(); + if (!m_has_stores_v.is_marked (lhs)) { + std::swap (lhs, rhs); + } + if (m_has_stores_v.is_marked (lhs)) { + /** project using the equivalence: + * + * (store(arr0,idx,x) ==I arr1) <-> + * + * (idx \in I => (arr0 ==I arr1)) /\ + * (idx \not\in I => (arr0 ==I+idx arr1) /\ (arr1[idx] == x))) + */ + vector I; + expr_ref_vector idxs (m); + p.get_diff_indices (I); + app* a_lhs = to_app (lhs); + expr* arr0 = a_lhs->get_arg (0); + idxs.append(a_lhs->get_num_args() - 2, a_lhs->get_args() + 1); + expr* x = a_lhs->get_arg (2); + expr* arr1 = rhs; + // check if (idx \in I) in M + bool idx_in_I = false; + expr_ref_vector idx_diseq (m); + if (!I.empty ()) { + expr_ref_vector vals = (*m_mev)(idxs); + for (unsigned i = 0; i < I.size () && !idx_in_I; i++) { + if (is_eq(idxs, I.get(i))) { + idx_in_I = true; } - break; - case l_false: { - ptr_vector new_args; - if (i > 0) { - m_lits.resize(m_lits.size() - i); + else { + expr_ref idx_eq = mk_eq(idxs, I[i]); + expr_ref_vector vals1 = (*m_mev)(I[i]); + if (is_eq(vals, vals1)) { + idx_in_I = true; + m_idx_lits_v.push_back (idx_eq); + } + else { + idx_diseq.push_back (m.mk_not (idx_eq)); + } } - m_lits.push_back(m.mk_not(m.mk_eq(arg1, arg2))); - new_args.push_back(b->get_arg(0)); - new_args.append(n-1, args+1); - result = m.mk_app(f, n, new_args.c_ptr()); - return BR_REWRITE1; - } - case l_undef: - return BR_FAILED; } } - result = b->get_arg(n); - return BR_DONE; + if (idx_in_I) { + TRACE ("qe", + tout << "store index in diff indices:\n"; + tout << mk_pp (m_idx_lits_v.back (), m) << "\n"; + ); + + // arr0 ==I arr1 + p_exp = mk_peq (arr0, arr1, I); + + TRACE ("qe", + tout << "new peq:\n"; + tout << mk_pp (p_exp, m) << "\n"; + ); + } + else { + m_idx_lits_v.append (idx_diseq); + // arr0 ==I+idx arr1 + I.push_back (idxs); + p_exp = mk_peq (arr0, arr1, I); + + TRACE ("qe", tout << "new peq:\n" << p_exp << "\n"; ); + + // arr1[idx] == x + ptr_vector sel_args; + sel_args.push_back (arr1); + sel_args.append(idxs.size(), idxs.c_ptr()); + expr_ref arr1_idx (m_arr_u.mk_select (sel_args.size (), sel_args.c_ptr ()), m); + expr_ref eq (m.mk_eq (arr1_idx, x), m); + m_aux_lits_v.push_back (eq); + + TRACE ("qe", + tout << "new eq:\n"; + tout << mk_pp (eq, m) << "\n"; + ); + } + } + else if (lhs == rhs) { // trivial peq (a ==I a) + break; + } + else if (lhs == m_v || rhs == m_v) { + subst_eq_found = true; + TRACE ("qe", + tout << "subst eq found!\n"; + ); + break; + } + else { + UNREACHABLE (); } - return BR_FAILED; } - lbool compare(expr* x, expr* y) { - NOT_IMPLEMENTED_YET(); - return l_undef; + // factor out select terms on m_v from p_exp using fresh constants + if (subst_eq_found) { + factor_selects (p_exp); + + TRACE ("qe", + tout << "after factoring selects:\n"; + tout << mk_pp (p_exp, m) << "\n"; + for (unsigned i = m_aux_lits_v.size () - m_aux_vars.size (); i < m_aux_lits_v.size (); i++) { + tout << mk_pp (m_aux_lits_v.get (i), m) << "\n"; + } + ); + + // find subst_term + bool stores_on_rhs = true; + app* a = to_app (p_exp); + if (a->get_arg (1) == m_v) { + stores_on_rhs = false; + } + app_ref eq (m); + convert_peq_to_eq (p_exp, eq, stores_on_rhs); + m_subst_term_v = eq->get_arg (1); + + TRACE ("qe", + tout << "subst term found:\n"; + tout << mk_pp (m_subst_term_v, m) << "\n"; + ); + } + } + + /** + * compute nesting depths of stores on m_v in true_eqs, as follows: + * 0 if m_v appears on both sides of equality + * 1 if equality is (m_v=t) + * 2 if equality is (store(m_v,i,v)=t) + * ... + */ + unsigned get_nesting_depth(app* eq) { + SASSERT(m.is_eq(eq)); + expr* lhs = eq->get_arg (0); + expr* rhs = eq->get_arg (1); + bool lhs_has_v = (lhs == m_v || m_has_stores_v.is_marked (lhs)); + bool rhs_has_v = (rhs == m_v || m_has_stores_v.is_marked (rhs)); + app* store = nullptr; + + SASSERT (lhs_has_v || rhs_has_v); + + if (!lhs_has_v && is_app(rhs)) { + store = to_app (rhs); + } + else if (!rhs_has_v && is_app(lhs)) { + store = to_app (lhs); + } + else { + // v appears on both sides -- trivial equality + // put it in the beginning to simplify it away + return 0; + } + + unsigned nd = 0; // nesting depth + for (nd = 1; m_arr_u.is_store (store); nd++, store = to_app (store->get_arg (0))) + /* empty */ ; + SASSERT (store == m_v); + return nd; + } + + struct compare_nd { + bool operator()(std::pair const& x, std::pair const& y) const { + return x < y; } }; + /** + * try to substitute for m_v, using array equalities + * + * compute substitution term and aux lits + */ + bool project (expr_ref const& fml) { + app_ref_vector eqs (m); + svector > true_eqs; + + find_arr_eqs (fml, eqs); + TRACE ("qe", tout << "array equalities:\n" << eqs << "\n";); + + // evaluate eqs in M + for (app * eq : eqs) { + TRACE ("qe", tout << "array equality:\n" << mk_pp (eq, m) << "\n"; ); + + if (m_mev->is_false(eq)) { + m_false_sub_v.insert (eq, m.mk_false()); + } + else { + true_eqs.push_back (std::make_pair(get_nesting_depth(eq), eq)); + } + } + std::sort(true_eqs.begin(), true_eqs.end(), compare_nd()); + DEBUG_CODE(for (unsigned i = 0; i + 1 < true_eqs.size(); ++i) SASSERT(true_eqs[i].first <= true_eqs[i+1].first);); + + // search for subst term + for (unsigned i = 0; !m_subst_term_v && i < true_eqs.size(); i++) { + app* eq = true_eqs[i].second; + m_true_sub_v.insert (eq, m.mk_true ()); + // try to find subst term + find_subst_term (eq); + } + + return true; + } + + void mk_result (expr_ref& fml) { + th_rewriter rw(m); + rw (fml); + // add in aux_lits and idx_lits + expr_ref_vector lits (m); + // TODO: eliminate possible duplicates, especially in idx_lits + // theory rewriting is a possibility, but not sure if it + // introduces unwanted terms such as ite's + lits.append (m_idx_lits_v); + lits.append (m_aux_lits_v); + lits.push_back (fml); + fml = mk_and(lits); + + if (m_subst_term_v) { + m_true_sub_v.insert (m_v, m_subst_term_v); + m_true_sub_v (fml); + } + else { + m_true_sub_v (fml); + m_false_sub_v (fml); + } + rw(fml); + SASSERT (!m.is_false (fml)); + } + + public: + + array_project_eqs_util (ast_manager& m): + m (m), + m_arr_u (m), + m_v (m), + m_subst_term_v (m), + m_true_sub_v (m), + m_false_sub_v (m), + m_aux_lits_v (m), + m_idx_lits_v (m), + m_aux_vars (m) + {} + + void operator () (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { + reset (); + model_evaluator mev(mdl); + mev.set_model_completion(true); + M = &mdl; + m_mev = &mev; + + unsigned j = 0; + for (unsigned i = 0; i < arr_vars.size (); i++) { + reset_v (); + m_v = arr_vars.get (i); + if (!m_arr_u.is_array (m_v)) { + TRACE ("qe", + tout << "not an array variable: " << m_v << "\n"; + ); + aux_vars.push_back (m_v); + continue; + } + TRACE ("qe", tout << "projecting equalities on variable: " << m_v << "\n"; ); + + if (project (fml)) { + mk_result (fml); + + contains_app contains_v (m, m_v); + if (!m_subst_term_v || contains_v (m_subst_term_v)) { + arr_vars[j++] = m_v; + } + TRACE ("qe", tout << "after projection: \n" << fml << "\n";); + } + else { + IF_VERBOSE(2, verbose_stream() << "can't project:" << m_v << "\n";); + TRACE ("qe", tout << "Failed to project: " << m_v << "\n";); + arr_vars[j++] = m_v; + } + } + arr_vars.shrink(j); + aux_vars.append (m_aux_vars); + } + }; + + /** + * Eliminate (select (store ..) ..) redexes by evaluating indices under model M. + * This does not eliminate variables, but introduces additional constraints on index equalities. + */ + + class array_select_reducer { + ast_manager& m; + array_util m_arr_u; + obj_map m_cache; + expr_ref_vector m_pinned; // to ensure a reference + expr_ref_vector m_idx_lits; + model_ref M; + model_evaluator* m_mev; + th_rewriter m_rw; + ast_mark m_arr_test; + ast_mark m_has_stores; + bool m_reduce_all_selects; + + void reset () { + m_cache.reset (); + m_pinned.reset (); + m_idx_lits.reset (); + M = nullptr; + m_mev = nullptr; + m_arr_test.reset (); + m_has_stores.reset (); + m_reduce_all_selects = false; + } + + bool is_equals (expr *e1, expr *e2) { + return e1 == e2 || (*m_mev)(e1) == (*m_mev)(e2); + } + + bool is_equals (unsigned arity, expr * const* xs, expr * const * ys) { + for (unsigned i = 0; i < arity; ++i) { + if (!is_equals(xs[i], ys[i])) return false; + } + return true; + } + + expr_ref mk_eq(unsigned arity, expr * const* xs, expr * const * ys) { + expr_ref_vector r(m); + for (unsigned i = 0; i < arity; ++i) { + r.push_back(m.mk_eq(xs[i], ys[i])); + } + return mk_and(r); + } + + void add_idx_cond (expr_ref& cond) { + m_rw (cond); + if (!m.is_true (cond)) m_idx_lits.push_back (cond); + } + + bool has_stores (expr* e) { + if (m_reduce_all_selects) return true; + return m_has_stores.is_marked (e); + } + + void mark_stores (app* a, bool args_have_stores) { + if (m_reduce_all_selects) return; + if (args_have_stores || + (m_arr_u.is_store (a) && m_arr_test.is_marked (a->get_arg (0)))) { + m_has_stores.mark (a, true); + } + } + + bool reduce (expr_ref& e) { + if (!is_app (e)) return true; + + expr *r = nullptr; + if (m_cache.find (e, r)) { + e = r; + return true; + } + + ptr_vector todo; + todo.push_back (to_app (e)); + expr_ref_vector args (m); + + while (!todo.empty ()) { + app *a = todo.back (); + unsigned sz = todo.size (); + bool dirty = false; + bool args_have_stores = false; + args.reset(); + for (expr * arg : *a) { + expr *narg = nullptr; + if (!is_app (arg)) { + args.push_back (arg); + } + else if (m_cache.find (arg, narg)) { + args.push_back (narg); + dirty |= (arg != narg); + if (!args_have_stores && has_stores (narg)) { + args_have_stores = true; + } + } + else { + todo.push_back (to_app (arg)); + } + } + + if (todo.size () > sz) continue; + todo.pop_back (); + + if (dirty) { + r = m.mk_app (a->get_decl (), args.size (), args.c_ptr ()); + m_pinned.push_back (r); + } + else { + r = a; + } + + if (m_arr_u.is_select (r) && has_stores (to_app (r)->get_arg (0))) { + r = reduce_core (to_app(r)); + } + else { + mark_stores (to_app (r), args_have_stores); + } + + m_cache.insert (a, r); + } + + SASSERT (r); + e = r; + return true; + } + + /** + * \brief reduce (select (store (store x i1 v1) i2 v2) idx) under model M + * such that the result is v2 if idx = i2 under M, it is v1 if idx = i1, idx != i2 under M, + * and it is (select x idx) if idx != i1, idx !+ i2 under M. + */ + expr* reduce_core (app *a) { + if (!m_arr_u.is_store (a->get_arg (0))) return a; + expr* array = a->get_arg(0); + unsigned arity = get_array_arity(m.get_sort(array)); + + expr* const* js = a->get_args() + 1; + + while (m_arr_u.is_store (array)) { + a = to_app (array); + expr* const* idxs = a->get_args() + 1; + expr_ref cond = mk_eq(arity, idxs, js); + + if (is_equals (arity, idxs, js)) { + add_idx_cond (cond); + return a->get_arg (2); + } + else { + cond = m.mk_not (cond); + add_idx_cond (cond); + array = a->get_arg (0); + } + } + ptr_vector args; + args.push_back(array); + args.append(arity, js); + expr* r = m_arr_u.mk_select (args.size(), args.c_ptr()); + m_pinned.push_back (r); + return r; + } + + void mk_result (expr_ref& fml) { + // conjoin idx lits + expr_ref_vector lits (m); + lits.append (m_idx_lits); + lits.push_back (fml); + fml = mk_and(lits); + // simplify all trivial expressions introduced + m_rw (fml); + TRACE ("qe", tout << "after reducing selects:\n" << fml << "\n";); + } + + public: + + array_select_reducer (ast_manager& m): + m (m), + m_arr_u (m), + m_pinned (m), + m_idx_lits (m), + m_rw (m), + m_reduce_all_selects (false) + {} + + void operator () (model& mdl, app_ref_vector const& arr_vars, expr_ref& fml, bool reduce_all_selects = false) { + if (!reduce_all_selects && arr_vars.empty ()) return; + + reset (); + model_evaluator mev(mdl); + mev.set_model_completion(true); + M = &mdl; + m_mev = &mev; + m_reduce_all_selects = reduce_all_selects; + + // mark vars to eliminate + for (app* v : arr_vars) { + m_arr_test.mark (v, true); + } + + // assume all arr_vars are of array sort + // and assume no store equalities on arr_vars + if (reduce (fml)) { + mk_result (fml); + } + else { + IF_VERBOSE(2, verbose_stream() << "can't project arrays:" << "\n";); + TRACE ("qe", tout << "Failed to project arrays\n";); + } + } + }; + + /** + * Perform Ackerman reduction on arrays. + * for occurrences (select a i1), (select a i2), ... assuming these are all occurrences. + * - collect i1, i2, i3, into equivalence classes according to M + * - for distinct index classes accumulate constraint i1 < i2 < i3 .. (for arithmetic) + * and generally distinct(i1, i2, i3) for arbitrary index sorts. + * - for equal indices accumulate constraint i1 = i2, i3 = i5, .. + * - introduce variables v1, v2, .., for each equivalence class. + * - replace occurrences of select by v1, v2, ... + * - update M to evaluate v1, v2, the same as (select a i1) (select a i2) + */ + + class array_project_selects_util { + typedef obj_map*> sel_map; + + struct idx_val { + expr_ref_vector idx; + expr_ref_vector val; + vector rval; + idx_val(expr_ref_vector & idx, expr_ref_vector & val, vector const& rval): idx(idx), val(val), rval(rval) {} + idx_val& operator=(idx_val const& o) { + idx.reset(); val.reset(); rval.reset(); + idx.append(o.idx); val.append(o.val); rval.append(o.rval); + return *this; + } + }; + ast_manager& m; + array_util m_arr_u; + arith_util m_ari_u; + bv_util m_bv_u; + sel_map m_sel_terms; + // representative indices for eliminating selects + vector m_idxs; + app_ref_vector m_sel_consts; + expr_ref_vector m_idx_lits; + model_ref M; + model_evaluator* m_mev; + expr_safe_replace m_sub; + ast_mark m_arr_test; + + void reset () { + m_sel_terms.reset (); + m_idxs.reset(); + m_sel_consts.reset (); + m_idx_lits.reset (); + M = nullptr; + m_mev = nullptr; + m_sub.reset (); + m_arr_test.reset (); + } + + /** + * collect sel terms on array vars as given by m_arr_test + */ + void collect_selects (expr* fml) { + if (!is_app (fml)) return; + ast_mark done; + ptr_vector todo; + todo.push_back (to_app (fml)); + for (unsigned i = 0; i < todo.size(); ++i) { + app* a = todo[i]; + if (done.is_marked (a)) continue; + done.mark (a, true); + for (expr* arg : *a) { + if (!done.is_marked (arg) && is_app (arg)) { + todo.push_back (to_app (arg)); + } + } + if (m_arr_u.is_select (a)) { + expr* arr = a->get_arg (0); + if (m_arr_test.is_marked (arr)) { + ptr_vector* lst = m_sel_terms.find (to_app (arr));; + lst->push_back (a); + } + } + } + } + + vector to_num(expr_ref_vector const& vals) { + vector rs; + rational r; + for (expr* v : vals) { + if (m_bv_u.is_bv(v)) { + VERIFY (m_bv_u.is_numeral(v, r)); + } + else if (m_ari_u.is_real(v) || m_ari_u.is_int(v)) { + VERIFY (m_ari_u.is_numeral(v, r)); + } + else { + r.reset(); + } + rs.push_back(r); + } + return rs; + } + + struct compare_idx { + array_project_selects_util& u; + compare_idx(array_project_selects_util& u):u(u) {} + bool operator()(idx_val const& x, idx_val const& y) { + SASSERT(x.rval.size() == y.rval.size()); + for (unsigned j = 0; j < x.rval.size(); ++j) { + rational const& xv = x.rval[j]; + rational const& yv = y.rval[j]; + if (xv < yv) return true; + if (xv > yv) return false; + } + return false; + } + }; + + expr* mk_lt(expr* x, expr* y) { + if (m_bv_u.is_bv(x)) { + return m.mk_not(m_bv_u.mk_ule(y, x)); + } + else { + return m_ari_u.mk_lt(x, y); + } + } + + expr_ref mk_lex_lt(expr_ref_vector const& xs, expr_ref_vector const& ys) { + SASSERT(xs.size() == ys.size() && !xs.empty()); + expr_ref result(mk_lt(xs.back(), ys.back()), m); + for (unsigned i = xs.size()-1; i-- > 0; ) { + result = m.mk_or(mk_lt(xs[i], ys[i]), + m.mk_and(m.mk_eq(xs[i], ys[i]), result)); + } + return result; + } + + /** + * model based ackermannization for sel terms of some array + * + * update sub with val consts for sel terms + */ + void ackermann (ptr_vector const& sel_terms) { + if (sel_terms.empty ()) return; + + expr* v = sel_terms.get (0)->get_arg (0); // array variable + sort* v_sort = m.get_sort (v); + sort* val_sort = get_array_range (v_sort); + unsigned arity = get_array_arity(v_sort); + bool is_numeric = true; + for (unsigned i = 0; i < arity && is_numeric; ++i) { + sort* srt = get_array_domain(v_sort, i); + if (!m_ari_u.is_real(srt) && !m_ari_u.is_int(srt) && !m_bv_u.is_bv_sort(srt)) { + TRACE("qe", tout << "non-numeric index sort for Ackerman" << mk_pp(srt, m) << "\n";); + is_numeric = false; + } + } + + unsigned start = m_idxs.size (); // append at the end + for (app * a : sel_terms) { + expr_ref_vector idxs(m, arity, a->get_args() + 1); + expr_ref_vector vals = (*m_mev)(idxs); + bool is_new = true; + for (unsigned j = start; j < m_idxs.size (); j++) { + if (!is_eq(m_idxs[j].val, vals)) continue; + // idx belongs to the jth equivalence class; + // substitute sel term with ith sel const + expr* c = m_sel_consts.get (j); + m_sub.insert (a, c); + // add equality (idx == repr) + m_idx_lits.push_back (mk_eq (idxs, m_idxs[j].idx)); + is_new = false; + break; + } + if (is_new) { + // new repr, val, and sel const + vector rvals = to_num(vals); + m_idxs.push_back(idx_val(idxs, vals, rvals)); + app_ref c (m.mk_fresh_const ("sel", val_sort), m); + m_sel_consts.push_back (c); + // substitute sel term with new const + m_sub.insert (a, c); + // extend M to include c + expr_ref val = (*m_mev)(a); + M->register_decl (c->get_decl (), val); + } + } + + if (start + 1 == m_idxs.size()) { + // nothing to differentiate. + } + else if (is_numeric) { + // sort reprs by their value and add a chain of strict inequalities + compare_idx cmp(*this); + std::sort(m_idxs.begin() + start, m_idxs.end(), cmp); + for (unsigned i = start; i + 1 < m_idxs.size(); ++i) { + m_idx_lits.push_back (mk_lex_lt(m_idxs[i].idx, m_idxs[i+1].idx)); + } + } + else if (arity == 1) { + // create distinct constraint. + expr_ref_vector xs(m); + for (unsigned i = start; i < m_idxs.size(); ++i) { + xs.append(m_idxs[i].idx); + } + m_idx_lits.push_back(m.mk_distinct(xs.size(), xs.c_ptr())); + } + else { + datatype::util dt(m); + sort_ref_vector srts(m); + ptr_vector acc; + unsigned i = 0; + for (expr * x : m_idxs[0].idx) { + std::stringstream name; + name << "get" << (i++); + acc.push_back(mk_accessor_decl(m, symbol(name.str().c_str()), type_ref(m.get_sort(x)))); + } + constructor_decl* constrs[1] = { mk_constructor_decl(symbol("tuple"), symbol("is-tuple"), acc.size(), acc.c_ptr()) }; + datatype::def* dts = mk_datatype_decl(dt, symbol("tuple"), 0, nullptr, 1, constrs); + VERIFY(dt.get_plugin()->mk_datatypes(1, &dts, 0, nullptr, srts)); + del_datatype_decl(dts); + sort* tuple = srts.get(0); + ptr_vector const & decls = *dt.get_datatype_constructors(tuple); + expr_ref_vector xs(m); + for (unsigned i = start; i < m_idxs.size(); ++i) { + xs.push_back(m.mk_app(decls[0], m_idxs[i].idx.size(), m_idxs[i].idx.c_ptr())); + } + m_idx_lits.push_back(m.mk_distinct(xs.size(), xs.c_ptr())); + } + } + + void mk_result (expr_ref& fml) { + // conjoin idx lits + m_idx_lits.push_back(fml); + fml = mk_and (m_idx_lits); + + // substitute for sel terms + m_sub (fml); + + TRACE ("qe", tout << "after projection of selects:\n" << fml << "\n";); + } + + /** + * project selects + * populates idx lits and obtains substitution for sel terms + */ + bool project (expr* fml) { + // collect sel terms -- populate the map m_sel_terms + collect_selects (fml); + // model based ackermannization + for (auto & kv : m_sel_terms) { + TRACE ("qe",tout << "ackermann for var: " << mk_pp (kv.m_key, m) << "\n";); + ackermann (*(kv.m_value)); + } + TRACE ("qe", tout << "idx lits:\n" << m_idx_lits; ); + return true; + } + + public: + + array_project_selects_util (ast_manager& m): + m (m), + m_arr_u (m), + m_ari_u (m), + m_bv_u (m), + m_sel_consts (m), + m_idx_lits (m), + m_sub (m) + {} + + void operator () (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { + if (arr_vars.empty()) return; + reset (); + model_evaluator mev(mdl); + mev.set_model_completion(true); + M = &mdl; + m_mev = &mev; + + // mark vars to eliminate + // alloc empty map from array var to sel terms over it + for (app* v : arr_vars) { + m_arr_test.mark(v, true); + m_sel_terms.insert(v, alloc (ptr_vector)); + } + + // assume all arr_vars are of array sort + // and they only appear in select terms + if (project (fml)) { + mk_result (fml); + aux_vars.append (m_sel_consts); + arr_vars.reset (); + } + else { + IF_VERBOSE(2, verbose_stream() << "can't project arrays:" << "\n";); + TRACE ("qe", tout << "Failed to project arrays\n";); + } + + // dealloc + for (auto & kv : m_sel_terms) dealloc(kv.m_value); + m_sel_terms.reset (); + } + }; + + + struct array_project_plugin::imp { + struct indices { expr_ref_vector m_values; expr* const* m_vars; - indices(ast_manager& m, model& model, unsigned n, expr* const* vars): + indices(ast_manager& m, model& model, unsigned n, expr* const* vars): m_values(m), m_vars(vars) { expr_ref val(m); - for (unsigned i = 0; i < n; ++i) { - VERIFY(model.eval(vars[i], val)); - m_values.push_back(val); + for (unsigned i = 0; i < n; ++i) { + m_values.push_back(model(vars[i])); } } }; - + ast_manager& m; array_util a; scoped_ptr m_var; - + imp(ast_manager& m): m(m), a(m) {} ~imp() {} @@ -114,159 +1198,25 @@ namespace qe { return false; } - bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) { - - TRACE("qe", tout << mk_pp(var, m) << "\n" << lits;); - m_var = alloc(contains_app, m, var); - - // reduce select-store redeces based on model. - // rw_cfg rw(m); - // rw(lits); - - // try first to solve for var. - if (solve_eq(model, vars, lits)) { - return true; - } - - app_ref_vector selects(m); - - // check that only non-select occurrences are in disequalities. - if (!check_diseqs(lits, selects)) { - TRACE("qe", tout << "Could not project " << mk_pp(var, m) << " for:\n" << lits << "\n";); - return false; - } - - // remove disequalities. - elim_diseqs(lits); - - // Ackerman reduction on remaining select occurrences - // either replace occurrences by model value or other node - // that is congruent to model value. - - ackermanize_select(model, selects, vars, lits); - - TRACE("qe", tout << selects << "\n" << lits << "\n";); - return true; - } - - void ackermanize_select(model& model, app_ref_vector const& selects, app_ref_vector& vars, expr_ref_vector& lits) { - expr_ref_vector vals(m), reps(m); - expr_ref val(m); - expr_safe_replace sub(m); - - if (selects.empty()) { - return; - } - - app_ref sel(m); - for (unsigned i = 0; i < selects.size(); ++i) { - sel = m.mk_fresh_const("sel", m.get_sort(selects[i])); - VERIFY (model.eval(selects[i], val)); - model.register_decl(sel->get_decl(), val); - vals.push_back(to_app(val)); - reps.push_back(val); // TODO: direct pass could handle nested selects. - vars.push_back(sel); - sub.insert(selects[i], val); - } - - sub(lits); - remove_true(lits); - project_plugin::partition_args(model, selects, lits); - project_plugin::partition_values(model, reps, lits); - } - void remove_true(expr_ref_vector& lits) { for (unsigned i = 0; i < lits.size(); ++i) { if (m.is_true(lits[i].get())) { project_plugin::erase(lits, i); } - } + } } bool contains_x(expr* e) { return (*m_var)(e); } - void mk_eq(indices& x, indices y, expr_ref_vector& lits) { + void mk_eq(indices const& x, indices const& y, expr_ref_vector& lits) { + SASSERT(x.m_values.size() == y.m_values.size()); unsigned n = x.m_values.size(); for (unsigned j = 0; j < n; ++j) { lits.push_back(m.mk_eq(x.m_vars[j], y.m_vars[j])); } } - - // check that x occurs only under selects or in disequalities. - bool check_diseqs(expr_ref_vector const& lits, app_ref_vector& selects) { - expr_mark mark; - ptr_vector todo; - app* e; - for (unsigned i = 0; i < lits.size(); ++i) { - e = to_app(lits[i]); - if (is_diseq_x(e)) { - continue; - } - if (contains_x(e)) { - todo.push_back(e); - } - } - while (!todo.empty()) { - e = todo.back(); - todo.pop_back(); - if (mark.is_marked(e)) { - continue; - } - mark.mark(e); - if (m_var->x() == e) { - return false; - } - unsigned start = 0; - if (a.is_select(e)) { - if (e->get_arg(0) == m_var->x()) { - start = 1; - selects.push_back(e); - } - } - for (unsigned i = start; i < e->get_num_args(); ++i) { - todo.push_back(to_app(e->get_arg(i))); - } - } - return true; - } - - void elim_diseqs(expr_ref_vector& lits) { - for (unsigned i = 0; i < lits.size(); ++i) { - if (is_diseq_x(lits[i].get())) { - project_plugin::erase(lits, i); - } - } - } - - bool is_update_x(app* e) { - do { - if (m_var->x() == e) { - return true; - } - if (a.is_store(e) && contains_x(e->get_arg(0))) { - for (unsigned i = 1; i < e->get_num_args(); ++i) { - if (contains_x(e->get_arg(i))) { - return false; - } - } - e = to_app(e->get_arg(0)); - continue; - } - } - while (false); - return false; - } - - bool is_diseq_x(expr* e) { - expr *f, * s, *t; - if (m.is_not(e, f) && m.is_eq(f, s, t)) { - if (contains_x(s) && !contains_x(t) && is_update_x(to_app(s))) return true; - if (contains_x(t) && !contains_x(s) && is_update_x(to_app(t))) return true; - } - return false; - } bool solve_eq(model& model, app_ref_vector& vars, expr_ref_vector& lits) { // find an equality to solve for. @@ -314,13 +1264,13 @@ namespace qe { var = m.mk_fresh_const("value", range); vars.push_back(var); args.reset(); - + args.push_back (s); args.append(idxs[i].m_values.size(), idxs[i].m_vars); sel = a.mk_select (args.size (), args.c_ptr ()); - VERIFY (model.eval (sel, val)); + val = model(sel); model.register_decl (var->get_decl (), val); - + args[0] = result; args.push_back(var); result = a.mk_store(args.size(), args.c_ptr()); @@ -341,7 +1291,7 @@ namespace qe { if (contains_x(s->get_arg(i))) { return false; } - } + } unsigned i; expr_ref_vector args(m); switch (contains(idx, idxs, i)) { @@ -361,7 +1311,7 @@ namespace qe { return solve(model, to_app(s->get_arg(0)), t, idxs, vars, lits); case l_undef: return false; - } + } } return false; } @@ -369,13 +1319,13 @@ namespace qe { lbool contains(indices const& idx, vector const& idxs, unsigned& j) { for (unsigned i = 0; i < idxs.size(); ++i) { switch (compare(idx, idxs[i])) { - case l_true: + case l_true: j = i; return l_true; case l_false: break; case l_undef: - return l_undef; + return l_undef; } } return l_false; @@ -399,36 +1349,72 @@ namespace qe { lbool compare(expr* val1, expr* val2) { if (m.are_equal (val1, val2)) return l_true; if (m.are_distinct (val1, val2)) return l_false; - + if (is_uninterp(val1) || is_uninterp(val2)) { // TBD chase definition of nested array. return l_undef; } return l_undef; - } + } }; - - + + array_project_plugin::array_project_plugin(ast_manager& m) { m_imp = alloc(imp, m); } - + array_project_plugin::~array_project_plugin() { dealloc(m_imp); } - + bool array_project_plugin::operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) { - return (*m_imp)(model, var, vars, lits); + ast_manager& m = vars.get_manager(); + app_ref_vector vvars(m, 1, &var); + expr_ref fml = mk_and(lits); + (*this)(model, vvars, fml, vars, false); + lits.reset(); + flatten_and(fml, lits); + return true; } bool array_project_plugin::solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) { return m_imp->solve(model, vars, lits); } - + family_id array_project_plugin::get_family_id() { return m_imp->a.get_family_id(); } -}; + void array_project_plugin::operator()(model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects) { + // 1. project array equalities + ast_manager& m = fml.get_manager(); + array_project_eqs_util pe (m); + pe (mdl, arr_vars, fml, aux_vars); + TRACE ("qe", + tout << "Projected array eqs: " << fml << "\n"; + tout << "Remaining array vars: " << arr_vars << "\n"; + tout << "Aux vars: " << aux_vars << "\n"; + ); + // 2. reduce selects + array_select_reducer rs (m); + rs (mdl, arr_vars, fml, reduce_all_selects); + + TRACE ("qe", tout << "Reduced selects:\n" << fml << "\n"; ); + + // 3. project selects using model based ackermannization + array_project_selects_util ps (m); + ps (mdl, arr_vars, fml, aux_vars); + + TRACE ("qe", + tout << "Projected array selects: " << fml << "\n"; + tout << "All aux vars: " << aux_vars << "\n"; + ); + } + + vector array_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { + return vector(); + } + +}; diff --git a/src/qe/qe_arrays.h b/src/qe/qe_arrays.h index a955250a8..3bb90335d 100644 --- a/src/qe/qe_arrays.h +++ b/src/qe/qe_arrays.h @@ -34,7 +34,9 @@ namespace qe { ~array_project_plugin() override; bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override; bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; + void operator()(model& model, app_ref_vector& vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects); family_id get_family_id() override; + vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; }; }; diff --git a/src/qe/qe_bool_plugin.cpp b/src/qe/qe_bool_plugin.cpp index fba76a7e6..f8bac21c8 100644 --- a/src/qe/qe_bool_plugin.cpp +++ b/src/qe/qe_bool_plugin.cpp @@ -93,10 +93,8 @@ namespace qe { bool solve_units(conj_enum& conjs, expr* _fml) { expr_ref fml(_fml, m); - conj_enum::iterator it = conjs.begin(), end = conjs.end(); unsigned idx; - for (; it != end; ++it) { - expr* e = *it; + for (expr * e : conjs) { if (!is_app(e)) { continue; } @@ -138,13 +136,11 @@ namespace qe { return false; } else if (p && !n) { - atom_set::iterator it = m_ctx.pos_atoms().begin(), end = m_ctx.pos_atoms().end(); - for (; it != end; ++it) { - if (x != *it && contains_x(*it)) return false; + for (expr* y : m_ctx.pos_atoms()) { + if (x != y && contains_x(y)) return false; } - it = m_ctx.neg_atoms().begin(), end = m_ctx.neg_atoms().end(); - for (; it != end; ++it) { - if (contains_x(*it)) return false; + for (expr* y : m_ctx.neg_atoms()) { + if (contains_x(y)) return false; } // only occurrences of 'x' must be in positive atoms def = m.mk_true(); @@ -152,13 +148,11 @@ namespace qe { return true; } else if (!p && n) { - atom_set::iterator it = m_ctx.pos_atoms().begin(), end = m_ctx.pos_atoms().end(); - for (; it != end; ++it) { - if (contains_x(*it)) return false; + for (expr* y : m_ctx.pos_atoms()) { + if (contains_x(y)) return false; } - it = m_ctx.neg_atoms().begin(), end = m_ctx.neg_atoms().end(); - for (; it != end; ++it) { - if (x != *it && contains_x(*it)) return false; + for (expr* y : m_ctx.neg_atoms()) { + if (x != y && contains_x(y)) return false; } def = m.mk_false(); m_replace.apply_substitution(x, def, fml); diff --git a/src/qe/qe_datatypes.cpp b/src/qe/qe_datatypes.cpp index 770beb59d..5499d638d 100644 --- a/src/qe/qe_datatypes.cpp +++ b/src/qe/qe_datatypes.cpp @@ -42,8 +42,7 @@ namespace qe { } bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) { - expr_ref val(m); - VERIFY(model.eval(var, val)); + expr_ref val = model(var); SASSERT(is_app(val)); TRACE("qe", tout << mk_pp(var, m) << " := " << val << "\n";); m_val = to_app(val); @@ -300,6 +299,9 @@ namespace qe { return m_imp->solve(model, vars, lits); } + vector datatype_project_plugin::project(model& model, app_ref_vector& vars, expr_ref_vector& lits) { + return vector(); + } family_id datatype_project_plugin::get_family_id() { return m_imp->dt.get_family_id(); diff --git a/src/qe/qe_datatypes.h b/src/qe/qe_datatypes.h index 9c8eb5a76..0483f4cce 100644 --- a/src/qe/qe_datatypes.h +++ b/src/qe/qe_datatypes.h @@ -35,6 +35,7 @@ namespace qe { bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) override; bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; family_id get_family_id() override; + vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) override; }; }; diff --git a/src/qe/qe_lite.cpp b/src/qe/qe_lite.cpp index 6ecdfd835..3b8d41316 100644 --- a/src/qe/qe_lite.cpp +++ b/src/qe/qe_lite.cpp @@ -36,18 +36,21 @@ Revision History: #include "ast/datatype_decl_plugin.h" #include "qe/qe_vartest.h" +#include "qe/qe_solve_plugin.h" namespace eq { bool occurs_var(unsigned idx, expr* e) { + if (is_ground(e)) return false; ptr_buffer todo; - todo.push_back(e); + todo.push_back(e); ast_mark mark; while (!todo.empty()) { expr* e = todo.back(); todo.pop_back(); if (mark.is_marked(e)) continue; mark.mark(e, true); + if (is_ground(e)) continue; if (is_var(e)) { if (to_var(e)->get_idx() == idx) return true; } @@ -67,15 +70,16 @@ namespace eq { arith_util a; datatype_util dt; is_variable_proc* m_is_variable; - var_subst m_subst; + beta_reducer m_subst; + expr_ref_vector m_subst_map; expr_ref_vector m_new_exprs; + plugin_manager m_solvers; ptr_vector m_map; int_vector m_pos2var; int_vector m_var2pos; ptr_vector m_inx2var; unsigned_vector m_order; - expr_ref_vector m_subst_map; expr_ref_buffer m_new_args; th_rewriter m_rewriter; params_ref m_params; @@ -149,7 +153,7 @@ namespace eq { done.mark(t); } } - done.mark(t); + done.mark(t); todo.pop_back(); break; case AST_QUANTIFIER: @@ -219,97 +223,6 @@ namespace eq { } } - bool is_invertible_const(bool is_int, expr* x, rational& a_val) { - expr* y; - if (a.is_uminus(x, y) && is_invertible_const(is_int, y, a_val)) { - a_val.neg(); - return true; - } - else if (a.is_numeral(x, a_val) && !a_val.is_zero()) { - if (!is_int || a_val.is_one() || a_val.is_minus_one()) { - return true; - } - } - return false; - } - - bool is_invertible_mul(bool is_int, expr*& arg, rational& a_val) { - if (is_variable(arg)) { - a_val = rational(1); - return true; - } - expr* x, *y; - if (a.is_mul(arg, x, y)) { - if (is_variable(x) && is_invertible_const(is_int, y, a_val)) { - arg = x; - return true; - } - if (is_variable(y) && is_invertible_const(is_int, x, a_val)) { - arg = y; - return true; - } - } - return false; - } - - typedef std::pair signed_expr; - - expr_ref solve_arith(bool is_int, rational const& r, bool sign, svector const& exprs) { - expr_ref_vector result(m); - for (unsigned i = 0; i < exprs.size(); ++i) { - bool sign2 = exprs[i].first; - expr* e = exprs[i].second; - rational r2(r); - if (sign == sign2) { - r2.neg(); - } - if (!r2.is_one()) { - result.push_back(a.mk_mul(a.mk_numeral(r2, is_int), e)); - } - else { - result.push_back(e); - } - } - return expr_ref(a.mk_add(result.size(), result.c_ptr()), m); - } - - bool solve_arith(expr* lhs, expr* rhs, ptr_vector& vs, expr_ref_vector& ts) { - if (!a.is_int(lhs) && !a.is_real(rhs)) { - return false; - } - rational a_val; - bool is_int = a.is_int(lhs); - svector todo, done; - todo.push_back(std::make_pair(true, lhs)); - todo.push_back(std::make_pair(false, rhs)); - while (!todo.empty()) { - expr* e = todo.back().second; - bool sign = todo.back().first; - todo.pop_back(); - if (a.is_add(e)) { - for (unsigned i = 0; i < to_app(e)->get_num_args(); ++i) { - todo.push_back(std::make_pair(sign, to_app(e)->get_arg(i))); - } - } - else if (is_invertible_mul(is_int, e, a_val)) { - done.append(todo); - vs.push_back(to_var(e)); - a_val = rational(1)/a_val; - ts.push_back(solve_arith(is_int, a_val, sign, done)); - TRACE("qe_lite", tout << mk_pp(lhs, m) << " " << mk_pp(rhs, m) << " " << mk_pp(e, m) << " := " << mk_pp(ts.back(), m) << "\n";); - return true; - } - else { - done.push_back(std::make_pair(sign, e)); - } - } - return false; - } - - bool arith_solve(expr * lhs, expr * rhs, expr * eq, ptr_vector& vs, expr_ref_vector& ts) { - return solve_arith(lhs, rhs, vs, ts); - } - bool trivial_solve(expr* lhs, expr* rhs, expr* eq, ptr_vector& vs, expr_ref_vector& ts) { if (!is_variable(lhs)) { std::swap(lhs, rhs); @@ -341,65 +254,25 @@ namespace eq { */ bool is_var_eq(expr * e, ptr_vector& vs, expr_ref_vector & ts) { - expr* lhs, *rhs; - var* v; + expr* lhs = nullptr, *rhs = nullptr; // (= VAR t), (iff VAR t), (iff (not VAR) t), (iff t (not VAR)) cases - if (m.is_eq(e, lhs, rhs) || m.is_iff(e, lhs, rhs)) { - // (iff (not VAR) t) (iff t (not VAR)) cases - if (!is_variable(lhs) && !is_variable(rhs) && m.is_bool(lhs)) { - if (!is_neg_var(m, lhs, v)) { - std::swap(lhs, rhs); - } - if (!is_neg_var(m, lhs, v)) { - return false; - } - vs.push_back(v); - ts.push_back(m.mk_not(rhs)); - TRACE("qe_lite", tout << mk_pp(e, m) << "\n";); - return true; - } - if (trivial_solve(lhs, rhs, e, vs, ts)) { - return true; - } - if (arith_solve(lhs, rhs, e, vs, ts)) { - return true; - } - return false; - } - - // (ite cond (= VAR t) (= VAR t2)) case - expr* cond, *e2, *e3; - if (m.is_ite(e, cond, e2, e3)) { - if (is_var_eq(e2, vs, ts)) { - expr_ref_vector ts2(m); - ptr_vector vs2; - if (is_var_eq(e3, vs2, ts2) && same_vars(vs, vs2)) { - for (unsigned i = 0; i < vs.size(); ++i) { - ts[i] = m.mk_ite(cond, ts[i].get(), ts2[i].get()); - } - return true; - } - } - return false; - } - - // VAR = true case - if (is_variable(e)) { - ts.push_back(m.mk_true()); - vs.push_back(to_var(e)); - TRACE("qe_lite", tout << mk_pp(e, m) << "\n";); + if (m.is_eq(e, lhs, rhs) && trivial_solve(lhs, rhs, e, vs, ts)) { return true; } - - // VAR = false case - if (is_neg_var(m, e, v)) { - ts.push_back(m.mk_false()); - vs.push_back(v); - TRACE("qe_lite", tout << mk_pp(e, m) << "\n";); - return true; + family_id fid = get_sort(e)->get_family_id(); + if (m.is_eq(e, lhs, rhs)) { + fid = get_sort(lhs)->get_family_id(); + } + qe::solve_plugin* p = m_solvers.get_plugin(fid); + if (p) { + expr_ref res = (*p)(e); + if (res != e && m.is_eq(res, lhs, rhs) && is_variable(lhs)) { + vs.push_back(to_var(lhs)); + ts.push_back(rhs); + return true; + } } - return false; } @@ -426,26 +299,29 @@ namespace eq { TRACE("qe_lite", tout << "Elimination m_order:" << std::endl; - for(unsigned i=0; iget_num_patterns(); j++) { expr_ref new_pat(m); - m_subst(q->get_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_pat); + m_subst(q->get_pattern(j), new_pat); new_patterns.push_back(new_pat); } for (unsigned j = 0; j < q->get_num_no_patterns(); j++) { expr_ref new_nopat(m); - m_subst(q->get_no_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_nopat); + m_subst(q->get_no_pattern(j), new_nopat); new_no_patterns.push_back(new_nopat); } @@ -748,7 +624,7 @@ namespace eq { expr_ref r(m), new_r(m); r = m.mk_and(conjs.size(), conjs.c_ptr()); create_substitution(largest_vinx + 1); - m_subst(r, m_subst_map.size(), m_subst_map.c_ptr(), new_r); + m_subst(r, new_r); m_rewriter(new_r); conjs.reset(); flatten_and(new_r, conjs); @@ -776,13 +652,19 @@ namespace eq { dt(m), m_is_variable(nullptr), m_subst(m), - m_new_exprs(m), m_subst_map(m), + m_new_exprs(m), m_new_args(m), m_rewriter(m), - m_params(p) {} + m_params(p) { + } - void set_is_variable_proc(is_variable_proc& proc) { m_is_variable = &proc;} + void set_is_variable_proc(is_variable_proc& proc) { + m_is_variable = &proc; + m_solvers.reset(); + m_solvers.register_plugin(qe::mk_arith_solve_plugin(m, proc)); + m_solvers.register_plugin(qe::mk_basic_solve_plugin(m, proc)); + } void operator()(quantifier * q, expr_ref & r, proof_ref & pr) { TRACE("qe_lite", tout << mk_pp(q, m) << "\n";); diff --git a/src/qe/qe_mbi.cpp b/src/qe/qe_mbi.cpp new file mode 100644 index 000000000..86fc1ac3c --- /dev/null +++ b/src/qe/qe_mbi.cpp @@ -0,0 +1,545 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + qe_mbi.cpp + +Abstract: + + Model-based interpolation utilities + +Author: + + Nikolaj Bjorner (nbjorner), Arie Gurfinkel 2018-6-8 + +Revision History: + + +Notes: + + Reduction into: + T_EUF + T_LIRA + + Other theories: DT, ARR reduced to EUF + BV is EUF/Boolean. + + Purify EUF1 & LIRA1 & EUF2 & LIRA2 + + Then EUF1 & EUF2 |- false + LIRA1 & LIRA2 |- false + + Sketch of approach by example: + + A: s <= 2a <= t & f(a) = q + + B: t <= 2b <= s + 1 & f(b) != q + + 1. Extract arithmetic consequences of A over shared vocabulary. + + A -> s <= t & (even(t) | s < t) + + 2a. Send to B, have B solve shared variables with EUF_B. + epsilon b . B & A_pure + epsilon b . t <= 2b <= s + 1 & s <= t & (even(t) | s < t) + = t <= s + 1 & (even(t) | t <= s) & s <= t & (even(t) | s < t) + = even(t) & t = s + b := t div 2 + + B & A_pure -> B[b/t div 2] = f(t div 2) != q & t <= s + 1 + + 3a. Send purified result to A + A & B_pure -> false + + Invoke the ping-pong principle to extract interpolant. + + 2b. Solve for shared variables with EUF. + + epsilon a . A + = a := (s + 1) div 2 & s < t & f((s + 1) div 2) = q + + 3b. Send to B. Produces core + s < t & f((s + 1) div 2) = q + + 4b Solve again in arithmetic for shared variables with EUF. + + epsion a . A & (s >= t | f((s + 1) div 2) != q) + + a := t div 2 | s = t & f(t div 2) = q & even(t) + + Send to B, produces core (s != t | f(t div 2) != q) + + 5b. There is no longer a solution for A. A is unsat. + +--*/ + +#include "ast/ast_util.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/bool_rewriter.h" +#include "ast/arith_decl_plugin.h" +#include "model/model_evaluator.h" +#include "solver/solver.h" +#include "qe/qe_mbi.h" +#include "qe/qe_term_graph.h" +#include "qe/qe_arith.h" + + +namespace qe { + + lbool mbi_plugin::check(expr_ref_vector& lits, model_ref& mdl) { + while (true) { + switch ((*this)(lits, mdl)) { + case mbi_sat: + return l_true; + case mbi_unsat: + return l_false; + case mbi_undef: + return l_undef; + case mbi_augment: + break; + } + } + } + + + // ------------------------------- + // prop_mbi + + prop_mbi_plugin::prop_mbi_plugin(solver* s): mbi_plugin(s->get_manager()), m_solver(s) {} + + // sketch of propositional + + mbi_result prop_mbi_plugin::operator()(expr_ref_vector& lits, model_ref& mdl) { + lbool r = m_solver->check_sat(lits); + switch (r) { + case l_false: + lits.reset(); + m_solver->get_unsat_core(lits); + return mbi_unsat; + case l_true: + m_solver->get_model(mdl); + lits.reset(); + for (unsigned i = 0, sz = mdl->get_num_constants(); i < sz; ++i) { + func_decl* c = mdl->get_constant(i); + if (m_shared.contains(c)) { + if (m.is_true(mdl->get_const_interp(c))) { + lits.push_back(m.mk_const(c)); + } + else if (m.is_false(mdl->get_const_interp(c))) { + lits.push_back(m.mk_not(m.mk_const(c))); + } + } + } + return mbi_sat; + default: + return mbi_undef; + } + } + + void prop_mbi_plugin::block(expr_ref_vector const& lits) { + m_solver->assert_expr(mk_not(mk_and(lits))); + } + + // ------------------------------- + // euf_mbi + + struct euf_mbi_plugin::is_atom_proc { + ast_manager& m; + expr_ref_vector& m_atoms; + is_atom_proc(expr_ref_vector& atoms): m(atoms.m()), m_atoms(atoms) {} + void operator()(app* a) { + if (m.is_eq(a)) { + m_atoms.push_back(a); + } + else if (m.is_bool(a) && a->get_family_id() != m.get_basic_family_id()) { + m_atoms.push_back(a); + } + } + void operator()(expr*) {} + }; + + euf_mbi_plugin::euf_mbi_plugin(solver* s, solver* sNot): + mbi_plugin(s->get_manager()), + m_atoms(m), + m_solver(s), + m_dual_solver(sNot) { + params_ref p; + p.set_bool("core.minimize", true); + m_solver->updt_params(p); + m_dual_solver->updt_params(p); + expr_ref_vector fmls(m); + m_solver->get_assertions(fmls); + expr_fast_mark1 marks; + is_atom_proc proc(m_atoms); + for (expr* e : fmls) { + quick_for_each_expr(proc, marks, e); + } + } + + mbi_result euf_mbi_plugin::operator()(expr_ref_vector& lits, model_ref& mdl) { + lbool r = m_solver->check_sat(lits); + switch (r) { + case l_false: + lits.reset(); + m_solver->get_unsat_core(lits); + // optionally minimize core using superposition. + return mbi_unsat; + case l_true: { + m_solver->get_model(mdl); + model_evaluator mev(*mdl.get()); + lits.reset(); + for (expr* e : m_atoms) { + if (mev.is_true(e)) { + lits.push_back(e); + } + else if (mev.is_false(e)) { + lits.push_back(m.mk_not(e)); + } + } + TRACE("qe", tout << "atoms from model: " << lits << "\n";); + r = m_dual_solver->check_sat(lits); + expr_ref_vector core(m); + term_graph tg(m); + switch (r) { + case l_false: + // use the dual solver to find a 'small' implicant + m_dual_solver->get_unsat_core(core); + TRACE("qe", tout << "core: " << core << "\n";); + // project the implicant onto vars + tg.set_vars(m_shared, false); + tg.add_lits(core); + lits.reset(); + lits.append(tg.project(*mdl)); + TRACE("qe", tout << "project: " << lits << "\n";); + return mbi_sat; + case l_undef: + return mbi_undef; + case l_true: + UNREACHABLE(); + return mbi_undef; + } + return mbi_sat; + } + default: + // TBD: if not running solver to completion, then: + // 1. extract unit literals from m_solver. + // 2. run a cc over the units + // 3. extract equalities or other assignments over the congruence classes + // 4. ensure that at least some progress is made over original lits. + return mbi_undef; + } + } + + void euf_mbi_plugin::block(expr_ref_vector const& lits) { + m_solver->assert_expr(mk_not(mk_and(lits))); + } + + + // ------------------------------- + // euf_arith_mbi + + struct euf_arith_mbi_plugin::is_atom_proc { + ast_manager& m; + expr_ref_vector& m_atoms; + is_atom_proc(expr_ref_vector& atoms): m(atoms.m()), m_atoms(atoms) {} + void operator()(app* a) { + if (m.is_eq(a)) { + m_atoms.push_back(a); + } + else if (m.is_bool(a) && a->get_family_id() != m.get_basic_family_id()) { + m_atoms.push_back(a); + } + } + void operator()(expr*) {} + }; + + struct euf_arith_mbi_plugin::is_arith_var_proc { + ast_manager& m; + app_ref_vector& m_vars; + arith_util arith; + obj_hashtable m_exclude; + is_arith_var_proc(app_ref_vector& vars, func_decl_ref_vector const& shared): + m(vars.m()), m_vars(vars), arith(m) { + for (func_decl* f : shared) m_exclude.insert(f); + } + void operator()(app* a) { + if (arith.is_int_real(a) && a->get_family_id() != arith.get_family_id() && !m_exclude.contains(a->get_decl())) { + m_vars.push_back(a); + } + } + void operator()(expr*) {} + + }; + + euf_arith_mbi_plugin::euf_arith_mbi_plugin(solver* s, solver* sNot): + mbi_plugin(s->get_manager()), + m_atoms(m), + m_solver(s), + m_dual_solver(sNot) { + params_ref p; + p.set_bool("core.minimize", true); + m_solver->updt_params(p); + m_dual_solver->updt_params(p); + expr_ref_vector fmls(m); + m_solver->get_assertions(fmls); + expr_fast_mark1 marks; + is_atom_proc proc(m_atoms); + for (expr* e : fmls) { + quick_for_each_expr(proc, marks, e); + } + } + + bool euf_arith_mbi_plugin::get_literals(model_ref& mdl, expr_ref_vector& lits) { + model_evaluator mev(*mdl.get()); + lits.reset(); + for (expr* e : m_atoms) { + if (mev.is_true(e)) { + lits.push_back(e); + } + else if (mev.is_false(e)) { + lits.push_back(m.mk_not(e)); + } + } + TRACE("qe", tout << "atoms from model: " << lits << "\n";); + lbool r = m_dual_solver->check_sat(lits); + if (l_false == r) { + // use the dual solver to find a 'small' implicant + lits.reset(); + m_dual_solver->get_unsat_core(lits); + return true; + } + else { + return false; + } + } + + app_ref_vector euf_arith_mbi_plugin::get_arith_vars(expr_ref_vector const& lits) { + arith_util a(m); + app_ref_vector avars(m); + is_arith_var_proc _proc(avars, m_shared); + for_each_expr(_proc, lits); + return avars; + } + + mbi_result euf_arith_mbi_plugin::operator()(expr_ref_vector& lits, model_ref& mdl) { + lbool r = m_solver->check_sat(lits); + + switch (r) { + case l_false: + lits.reset(); + m_solver->get_unsat_core(lits); + TRACE("qe", tout << "unsat core: " << lits << "\n";); + // optionally minimize core using superposition. + return mbi_unsat; + case l_true: { + m_solver->get_model(mdl); + if (!get_literals(mdl, lits)) { + return mbi_undef; + } + app_ref_vector avars = get_arith_vars(lits); + + TRACE("qe", tout << "vars: " << avars << " lits: " << lits << "\n";); + + // 1. project arithmetic variables using mdl that satisfies core. + // ground any remaining arithmetic variables using model. + arith_project_plugin ap(m); + ap.set_check_purified(false); + + auto defs = ap.project(*mdl.get(), avars, lits); + // 2. Add the projected definitions to the remaining (euf) literals + for (auto const& def : defs) { + lits.push_back(m.mk_eq(def.var, def.term)); + } + TRACE("qe", tout << "# arith defs" << defs.size() << " avars: " << avars << " " << lits << "\n";); + + // 3. Project the remaining literals with respect to EUF. + term_graph tg(m); + tg.set_vars(m_shared, false); + tg.add_lits(lits); + lits.reset(); + lits.append(tg.project(*mdl)); + //lits.append(tg.project()); + TRACE("qe", tout << "project: " << lits << "\n";); + return mbi_sat; + } + default: + // TBD: if not running solver to completion, then: + // 1. extract unit literals from m_solver. + // 2. run a cc over the units + // 3. extract equalities or other assignments over the congruence classes + // 4. ensure that at least some progress is made over original lits. + return mbi_undef; + } + } + + void euf_arith_mbi_plugin::block(expr_ref_vector const& lits) { + m_solver->assert_expr(mk_not(mk_and(lits))); + } + + + /** -------------------------------------------------------------- + * ping-pong interpolation of Gurfinkel & Vizel + * compute a binary interpolant. + */ + lbool interpolator::pingpong(mbi_plugin& a, mbi_plugin& b, expr_ref& itp) { + model_ref mdl; + expr_ref_vector lits(m); + bool turn = true; + vector itps, blocks; + itps.push_back(expr_ref_vector(m)); + itps.push_back(expr_ref_vector(m)); + blocks.push_back(expr_ref_vector(m)); + blocks.push_back(expr_ref_vector(m)); + mbi_result last_res = mbi_undef; + bool_rewriter rw(m); + while (true) { + auto* t1 = turn ? &a : &b; + auto* t2 = turn ? &b : &a; + mbi_result next_res = (*t1)(lits, mdl); + switch (next_res) { + case mbi_sat: + if (last_res == mbi_sat) { + itp = nullptr; + return l_true; + } + TRACE("mbi", tout << "new lits " << lits << "\n";); + break; // continue + case mbi_unsat: { + if (lits.empty()) { + // TBD, either a => itp and itp => !b + // or b => itp and itp => !a + itp = mk_and(itps[!turn]); + return l_false; + } + t2->block(lits); + expr_ref lemma(mk_not(mk_and(lits))); + TRACE("mbi", tout << lemma << "\n";); + blocks[turn].push_back(lemma); + itp = m.mk_implies(mk_and(blocks[!turn]), lemma); + // TBD: compute closure over variables not in vars + itps[turn].push_back(itp); + lits.reset(); // or find a prefix of lits? + break; + } + case mbi_augment: + break; + case mbi_undef: + return l_undef; + } + turn = !turn; + last_res = next_res; + } + } + + /** + * One-sided pogo creates clausal interpolants. + * It creates a set of consequences of b that are inconsistent with a. + */ + lbool interpolator::pogo(mbi_plugin& a, mbi_plugin& b, expr_ref& itp) { + expr_ref_vector lits(m), itps(m); + while (true) { + model_ref mdl; + lits.reset(); + switch (a.check(lits, mdl)) { + case l_true: + switch (b.check(lits, mdl)) { + case l_true: + return l_true; + case l_false: + a.block(lits); + itps.push_back(mk_not(mk_and(lits))); + break; + case l_undef: + return l_undef; + } + break; + case l_false: + itp = mk_and(itps); + return l_false; + case l_undef: + return l_undef; + } + } + } + + lbool interpolator::vurtego(mbi_plugin& a, mbi_plugin& b, expr_ref& itp, model_ref &mdl) { + /** + Assumptions on mbi_plugin() + Let local be assertions local to the plugin + Let blocked be clauses added by blocked, kept separately from local + mbi_plugin::check(lits, mdl, bool force_model): + if lits.empty() and mdl == nullptr then + if is_sat(local & blocked) then + return l_true, mbp of local, mdl of local & blocked + else + return l_false + else if !lits.empty() then + if is_sat(local & mdl & blocked) + return l_true, lits, extension of mdl to local + else if is_sat(local & lits & blocked) + if (force_model) then + return l_false, core of model, nullptr + else + return l_true, mbp of local, mdl of local & blocked + else if !is_sat(local & lits) then + return l_false, mbp of local, nullptr + else if is_sat(local & lits) && !is_sat(local & lits & blocked) + MISSING CASE + MUST PRODUCE AN IMPLICANT OF LOCAL that is inconsistent with lits & blocked + in this case !is_sat(local & lits & mdl) and is_sat(mdl, blocked) + let mdl_blocked be lits of blocked that are true in mdl + return l_false, core of lits & mdl_blocked, nullptr + + mbi_plugin::block(phi): add phi to blocked + + probably should use the operator() instead of check. + mbi_augment -- means consistent with lits but not with the mdl + mbi_sat -- means consistent with lits and mdl + + */ + expr_ref_vector lits(m), itps(m); + while (true) { + // when lits.empty(), this picks an A-implicant consistent with B + // when !lits.empty(), checks whether mdl of shared vocab extends to A + bool force_model = !lits.empty(); + switch (a.check_ag(lits, mdl, force_model)) { + case l_true: + if (force_model) + // mdl is a model for a && b + return l_true; + switch (b.check_ag(lits, mdl, false)) { + case l_true: + /* can return true if know that b did not change + the model. For now, cycle back to A. */ + SASSERT(!lits.empty()); + SASSERT(mdl); + break; + case l_false: + // Force a different A-implicant + a.block(lits); + lits.reset(); + mdl.reset(); + break; + case l_undef: + return l_undef; + } + case l_false: + if (lits.empty()) { + // no more A-implicants, terminate + itp = mk_and(itps); + return l_false; + } + // force B to pick a different model or a different implicant + b.block(lits); + itps.push_back(mk_not(mk_and(lits))); + lits.reset(); + mdl.reset(); + break; + case l_undef: + return l_undef; + } + } + } + +}; diff --git a/src/qe/qe_mbi.h b/src/qe/qe_mbi.h new file mode 100644 index 000000000..22a0864ba --- /dev/null +++ b/src/qe/qe_mbi.h @@ -0,0 +1,135 @@ +/*++ +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + qe_mbi.h + +Abstract: + + Model-based interpolation utilities + +Author: + + Nikolaj Bjorner (nbjorner), Arie Gurfinkel 2018-6-8 + +Revision History: + + +--*/ + +#pragma once + +namespace qe { + enum mbi_result { + mbi_sat, + mbi_unsat, + mbi_augment, + mbi_undef, + }; + + class mbi_plugin { + protected: + ast_manager& m; + func_decl_ref_vector m_shared; + public: + mbi_plugin(ast_manager& m): m(m), m_shared(m) {} + virtual ~mbi_plugin() {} + + /** + * Set the shared symbols. + */ + virtual void set_shared(func_decl_ref_vector const& vars) { + m_shared.reset(); + m_shared.append(vars); + } + + /** + * \brief Utility that works modulo a background state. + * - vars + * variables to preferrably project onto (other variables would require quantification to fit interpolation signature) + * - lits + * set of literals to check satisfiability with respect to. + * - mdl + * optional model for caller. + * on return: + * - mbi_sat: + * populates mdl with a satisfying state, and lits with implicant for background state. + * - mbi_unsat: + * populates lits to be inconsistent with background state. + * For all practical purposes it is a weakening of lits or even a subset of lits. + * - mbi_augment: + * populates lits with strengthening of lits (superset) + * - mbi_undef: + * inconclusive, + */ + virtual mbi_result operator()(expr_ref_vector& lits, model_ref& mdl) = 0; + + /** + * \brief Block conjunction of lits from future mbi_augment or mbi_sat. + */ + virtual void block(expr_ref_vector const& lits) = 0; + + /** + * \brief perform a full check, consume internal auguments if necessary. + */ + lbool check(expr_ref_vector& lits, model_ref& mdl); + + virtual lbool check_ag(expr_ref_vector& lits, model_ref& mdl, bool force_model) { + return l_undef; + } + + + }; + + class prop_mbi_plugin : public mbi_plugin { + solver_ref m_solver; + public: + prop_mbi_plugin(solver* s); + ~prop_mbi_plugin() override {} + mbi_result operator()(expr_ref_vector& lits, model_ref& mdl) override; + void block(expr_ref_vector const& lits) override; + }; + + class euf_mbi_plugin : public mbi_plugin { + expr_ref_vector m_atoms; + solver_ref m_solver; + solver_ref m_dual_solver; + struct is_atom_proc; + public: + euf_mbi_plugin(solver* s, solver* sNot); + ~euf_mbi_plugin() override {} + mbi_result operator()(expr_ref_vector& lits, model_ref& mdl) override; + void block(expr_ref_vector const& lits) override; + }; + + class euf_arith_mbi_plugin : public mbi_plugin { + expr_ref_vector m_atoms; + solver_ref m_solver; + solver_ref m_dual_solver; + struct is_atom_proc; + struct is_arith_var_proc; + + app_ref_vector get_arith_vars(expr_ref_vector const& lits); + bool get_literals(model_ref& mdl, expr_ref_vector& lits); + + public: + euf_arith_mbi_plugin(solver* s, solver* sNot); + ~euf_arith_mbi_plugin() override {} + mbi_result operator()(expr_ref_vector& lits, model_ref& mdl) override; + void block(expr_ref_vector const& lits) override; + }; + + /** + * use cases for interpolation. + */ + class interpolator { + ast_manager& m; + public: + interpolator(ast_manager& m):m(m) {} + lbool pingpong(mbi_plugin& a, mbi_plugin& b, expr_ref& itp); + lbool pogo(mbi_plugin& a, mbi_plugin& b, expr_ref& itp); + lbool vurtego(mbi_plugin &a, mbi_plugin &b, expr_ref &itp, model_ref &mdl); + }; + +}; diff --git a/src/qe/qe_mbp.cpp b/src/qe/qe_mbp.cpp index 432569ff9..78dcbfd16 100644 --- a/src/qe/qe_mbp.cpp +++ b/src/qe/qe_mbp.cpp @@ -18,17 +18,20 @@ Revision History: --*/ +#include "ast/rewriter/expr_safe_replace.h" +#include "ast/ast_pp.h" +#include "ast/ast_util.h" +#include "ast/occurs.h" +#include "ast/rewriter/th_rewriter.h" +#include "ast/expr_functors.h" +#include "ast/for_each_expr.h" +#include "ast/scoped_proof.h" #include "qe/qe_mbp.h" #include "qe/qe_arith.h" #include "qe/qe_arrays.h" #include "qe/qe_datatypes.h" -#include "ast/rewriter/expr_safe_replace.h" -#include "ast/ast_pp.h" -#include "ast/ast_util.h" -#include "ast/rewriter/th_rewriter.h" -#include "model/model_v2_pp.h" -#include "ast/expr_functors.h" -#include "ast/for_each_expr.h" +#include "qe/qe_lite.h" +#include "model/model_pp.h" #include "model/model_evaluator.h" @@ -61,9 +64,9 @@ expr_ref project_plugin::pick_equality(ast_manager& m, model& model, expr* t) { expr_ref_vector vals(m); obj_map val2expr; app* alit = to_app(t); - for (unsigned i = 0; i < alit->get_num_args(); ++i) { - expr* e1 = alit->get_arg(i), *e2; - VERIFY(model.eval(e1, val)); + for (expr * e1 : *alit) { + expr *e2; + val = model(e1); if (val2expr.find(val, e2)) { return expr_ref(m.mk_eq(e1, e2), m); } @@ -74,40 +77,6 @@ expr_ref project_plugin::pick_equality(ast_manager& m, model& model, expr* t) { return expr_ref(nullptr, m); } -void project_plugin::partition_values(model& model, expr_ref_vector const& vals, expr_ref_vector& lits) { - ast_manager& m = vals.get_manager(); - expr_ref val(m); - expr_ref_vector trail(m), reps(m); - obj_map roots; - for (unsigned i = 0; i < vals.size(); ++i) { - expr* v = vals[i], *root; - VERIFY (model.eval(v, val)); - if (roots.find(val, root)) { - lits.push_back(m.mk_eq(v, root)); - } - else { - roots.insert(val, v); - trail.push_back(val); - reps.push_back(v); - } - } - if (reps.size() > 1) { - lits.push_back(mk_distinct(reps)); - } -} - -void project_plugin::partition_args(model& model, app_ref_vector const& selects, expr_ref_vector& lits) { - ast_manager& m = selects.get_manager(); - if (selects.empty()) return; - unsigned num_args = selects[0]->get_decl()->get_arity(); - for (unsigned j = 1; j < num_args; ++j) { - expr_ref_vector args(m); - for (unsigned i = 0; i < selects.size(); ++i) { - args.push_back(selects[i]->get_arg(j)); - } - project_plugin::partition_values(model, args, lits); - } -} void project_plugin::erase(expr_ref_vector& lits, unsigned& i) { lits[i] = lits.back(); @@ -123,11 +92,16 @@ void project_plugin::push_back(expr_ref_vector& lits, expr* e) { class mbp::impl { ast_manager& m; + params_ref m_params; th_rewriter m_rw; ptr_vector m_plugins; expr_mark m_visited; expr_mark m_bool_visited; + // parameters + bool m_reduce_all_selects; + bool m_dont_sub; + void add_plugin(project_plugin* p) { family_id fid = p->get_family_id(); SASSERT(!m_plugins.get(fid, 0)); @@ -166,12 +140,9 @@ class mbp::impl { } if (reduced) { unsigned j = 0; - for (unsigned i = 0; i < vars.size(); ++i) { - if (!is_rem.is_marked(vars[i].get())) { - if (i != j) { - vars[j] = vars[i].get(); - } - ++j; + for (app* v : vars) { + if (!is_rem.is_marked(v)) { + vars[j++] = v; } } vars.shrink(j); @@ -198,20 +169,13 @@ class mbp::impl { void filter_variables(model& model, app_ref_vector& vars, expr_ref_vector& lits, expr_ref_vector& unused_lits) { expr_mark lit_visited; project_plugin::mark_rec(lit_visited, lits); - unsigned j = 0; - for (unsigned i = 0; i < vars.size(); ++i) { - app* var = vars[i].get(); + for (app* var : vars) { if (lit_visited.is_marked(var)) { - if (i != j) { - vars[j] = vars[i].get(); + vars[j++] = var; } - ++j; } - } - if (vars.size() != j) { - vars.resize(j); - } + vars.shrink(j); } bool extract_bools(model_evaluator& eval, expr_ref_vector& fmls, expr* fml) { @@ -258,47 +222,124 @@ class mbp::impl { return found_bool; } - void project_bools(model& model, app_ref_vector& vars, expr_ref_vector& fmls) { + void project_bools(model& mdl, app_ref_vector& vars, expr_ref_vector& fmls) { expr_safe_replace sub(m); expr_ref val(m); + model_evaluator eval(mdl, m_params); + eval.set_model_completion(true); unsigned j = 0; for (unsigned i = 0; i < vars.size(); ++i) { app* var = vars[i].get(); if (m.is_bool(var)) { - VERIFY(model.eval(var, val)); - sub.insert(var, val); + sub.insert(var, eval(var)); } else { - if (j != i) { - vars[j] = vars[i].get(); + vars[j++] = var; } - ++j; } + if (j == vars.size()) { + return; } - if (j != vars.size()) { - vars.resize(j); + vars.shrink(j); j = 0; for (unsigned i = 0; i < fmls.size(); ++i) { - sub(fmls[i].get(), val); + expr* fml = fmls[i].get(); + sub(fml, val); m_rw(val); if (!m.is_true(val)) { - TRACE("qe", tout << mk_pp(fmls[i].get(), m) << " -> " << val << "\n";); - fmls[i] = val; - if (j != i) { - fmls[j] = fmls[i].get(); + TRACE("qe", tout << mk_pp(fml, m) << " -> " << val << "\n";); + fmls[j++] = val; } - ++j; } + fmls.shrink(j); } - if (j != fmls.size()) { - fmls.resize(j); + + + void subst_vars(model_evaluator& eval, app_ref_vector const& vars, expr_ref& fml) { + expr_safe_replace sub (m); + for (app * v : vars) { + sub.insert(v, eval(v)); + } + sub(fml); + } + + struct index_term_finder { + ast_manager& m; + array_util m_array; + app_ref m_var; + expr_ref_vector& m_res; + + index_term_finder (ast_manager &mgr, app* v, expr_ref_vector &res): + m(mgr), m_array (m), m_var (v, m), m_res (res) {} + + void operator() (var *n) {} + void operator() (quantifier *n) {} + void operator() (app *n) { + expr *e1, *e2; + if (m_array.is_select (n)) { + for (expr * arg : *n) { + if (m.get_sort(arg) == m.get_sort(m_var) && arg != m_var) + m_res.push_back (arg); + } + } + else if (m.is_eq(n, e1, e2)) { + if (e1 == m_var) + m_res.push_back(e2); + else if (e2 == m_var) + m_res.push_back(e1); } } + }; + + bool project_var (model_evaluator& eval, app* var, expr_ref& fml) { + expr_ref val = eval(var); + + TRACE ("mbqi_project_verbose", tout << "MBQI: var: " << mk_pp (var, m) << "\n" << "fml: " << fml << "\n";); + expr_ref_vector terms (m); + index_term_finder finder (m, var, terms); + for_each_expr (finder, fml); + + TRACE ("mbqi_project_verbose", tout << "terms:\n" << terms;); + + for (expr * term : terms) { + expr_ref tval = eval(term); + + TRACE ("mbqi_project_verbose", tout << "term: " << mk_pp (term, m) << " tval: " << tval << " val: " << val << "\n";); + + // -- if the term does not contain an occurrence of var + // -- and is in the same equivalence class in the model + if (tval == val && !occurs (var, term)) { + TRACE ("mbqi_project", + tout << "MBQI: replacing " << mk_pp (var, m) << " with " << mk_pp (term, m) << "\n";); + expr_safe_replace sub(m); + sub.insert (var, term); + sub (fml); + return true; + } + } + + TRACE ("mbqi_project", + tout << "MBQI: failed to eliminate " << mk_pp (var, m) << " from " << fml << "\n";); + + return false; + } + + void project_vars (model &M, app_ref_vector &vars, expr_ref &fml) { + model_evaluator eval(M); + eval.set_model_completion(false); + // -- evaluate to initialize eval cache + (void) eval (fml); + unsigned j = 0; + for (app * v : vars) { + if (!project_var (eval, v, fml)) { + vars[j++] = v; + } + } + vars.shrink(j); } public: - opt::inf_eps maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt) { arith_project_plugin arith(m); return arith.maximize(fmls, mdl, t, ge, gt); @@ -426,23 +467,30 @@ public: m_bool_visited.reset(); } - impl(ast_manager& m):m(m), m_rw(m) { + impl(ast_manager& m, params_ref const& p):m(m), m_params(p), m_rw(m) { add_plugin(alloc(arith_project_plugin, m)); add_plugin(alloc(datatype_project_plugin, m)); add_plugin(alloc(array_project_plugin, m)); + updt_params(p); } ~impl() { std::for_each(m_plugins.begin(), m_plugins.end(), delete_proc()); } + void updt_params(params_ref const& p) { + m_params.append(p); + m_reduce_all_selects = m_params.get_bool("reduce_all_selects", false); + m_dont_sub = m_params.get_bool("dont_sub", false); + } + void preprocess_solve(model& model, app_ref_vector& vars, expr_ref_vector& fmls) { extract_literals(model, fmls); bool change = true; while (change && !vars.empty()) { change = solve(model, vars, fmls); - for (unsigned i = 0; i < m_plugins.size(); ++i) { - if (m_plugins[i] && m_plugins[i]->solve(model, vars, fmls)) { + for (auto* p : m_plugins) { + if (p && p->solve(model, vars, fmls)) { change = true; } } @@ -451,8 +499,9 @@ public: bool validate_model(model& model, expr_ref_vector const& fmls) { expr_ref val(m); - for (unsigned i = 0; i < fmls.size(); ++i) { - VERIFY(model.eval(fmls[i], val) && m.is_true(val)); + model_evaluator eval(model); + for (expr * f : fmls) { + VERIFY(model.is_true(f)); } return true; } @@ -469,8 +518,7 @@ public: while (progress && !vars.empty() && !fmls.empty()) { app_ref_vector new_vars(m); progress = false; - for (unsigned i = 0; i < m_plugins.size(); ++i) { - project_plugin* p = m_plugins[i]; + for (project_plugin * p : m_plugins) { if (p) { (*p)(model, vars, fmls); } @@ -490,7 +538,7 @@ public: var = new_vars.back(); new_vars.pop_back(); expr_safe_replace sub(m); - VERIFY(model.eval(var, val)); + val = model(var); sub.insert(var, val); for (unsigned i = 0; i < fmls.size(); ++i) { sub(fmls[i].get(), tmp); @@ -517,28 +565,146 @@ public: TRACE("qe", tout << vars << " " << fmls << "\n";); } + void do_qe_lite(app_ref_vector& vars, expr_ref& fml) { + qe_lite qe(m, m_params, false); + qe (vars, fml); + m_rw (fml); + TRACE ("qe", tout << "After qe_lite:\n" << fml << "\n" << "Vars: " << vars << "\n";); + SASSERT (!m.is_false (fml)); + } + + void do_qe_bool(model& mdl, app_ref_vector& vars, expr_ref& fml) { + expr_ref_vector fmls(m); + fmls.push_back(fml); + project_bools(mdl, vars, fmls); + fml = mk_and(fmls); + } + + void spacer(app_ref_vector& vars, model& mdl, expr_ref& fml) { + TRACE ("qe", tout << "Before projection:\n" << fml << "\n" << "Vars: " << vars << "\n";); + + model_evaluator eval(mdl, m_params); + eval.set_model_completion(true); + app_ref_vector other_vars (m); + app_ref_vector array_vars (m); + array_util arr_u (m); + arith_util ari_u (m); + + flatten_and(fml); + + + while (!vars.empty()) { + + do_qe_lite(vars, fml); + + do_qe_bool(mdl, vars, fml); + + // sort out vars into bools, arith (int/real), and arrays + for (app* v : vars) { + if (arr_u.is_array(v)) { + array_vars.push_back (v); + } + else { + other_vars.push_back (v); + } + } + + TRACE ("qe", tout << "Array vars: " << array_vars << "\n";); + + vars.reset (); + + // project arrays + qe::array_project_plugin ap(m); + ap(mdl, array_vars, fml, vars, m_reduce_all_selects); + SASSERT (array_vars.empty ()); + m_rw (fml); + SASSERT (!m.is_false (fml)); + + TRACE ("qe", + tout << "extended model:\n"; + model_pp (tout, mdl); + tout << "Vars: " << vars << "\n"; + ); + } + + // project reals, ints and other variables. + if (!other_vars.empty ()) { + TRACE ("qe", tout << "Other vars: " << other_vars << "\n"; + model_pp(tout, mdl);); + + expr_ref_vector fmls(m); + flatten_and (fml, fmls); + + (*this)(false, other_vars, mdl, fmls); + fml = mk_and (fmls); + + TRACE ("qe", + tout << "Projected other vars:\n" << fml << "\n"; + tout << "Remaining other vars:\n" << other_vars << "\n";); + SASSERT (!m.is_false (fml)); + } + + if (!other_vars.empty ()) { + project_vars (mdl, other_vars, fml); + } + + // substitute any remaining other vars + if (!m_dont_sub && !other_vars.empty ()) { + subst_vars (eval, other_vars, fml); + TRACE ("qe", tout << "After substituting remaining other vars:\n" << fml << "\n";); + // an extra round of simplification because subst_vars is not simplifying + m_rw(fml); + other_vars.reset(); + } + + SASSERT(eval.is_true(fml)); + + vars.reset (); + vars.append(other_vars); + } + }; -mbp::mbp(ast_manager& m) { - m_impl = alloc(impl, m); +mbp::mbp(ast_manager& m, params_ref const& p) { + scoped_no_proof _sp (m); + m_impl = alloc(impl, m, p); } mbp::~mbp() { dealloc(m_impl); } +void mbp::updt_params(params_ref const& p) { + m_impl->updt_params(p); +} + +void mbp::get_param_descrs(param_descrs & r) { + r.insert("reduce_all_selects", CPK_BOOL, "(default: false) reduce selects"); + r.insert("dont_sub", CPK_BOOL, "(default: false) disable substitution of values for free variables"); +} + void mbp::operator()(bool force_elim, app_ref_vector& vars, model& mdl, expr_ref_vector& fmls) { + scoped_no_proof _sp (fmls.get_manager()); (*m_impl)(force_elim, vars, mdl, fmls); } +void mbp::spacer(app_ref_vector& vars, model& mdl, expr_ref& fml) { + scoped_no_proof _sp (fml.get_manager()); + m_impl->spacer(vars, mdl, fml); +} + void mbp::solve(model& model, app_ref_vector& vars, expr_ref_vector& fmls) { + scoped_no_proof _sp (fmls.get_manager()); m_impl->preprocess_solve(model, vars, fmls); } void mbp::extract_literals(model& model, expr_ref_vector& lits) { + scoped_no_proof _sp (lits.get_manager()); m_impl->extract_literals(model, lits); } + opt::inf_eps mbp::maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt) { + scoped_no_proof _sp (fmls.get_manager()); return m_impl->maximize(fmls, mdl, t, ge, gt); } diff --git a/src/qe/qe_mbp.h b/src/qe/qe_mbp.h index d1695843c..0bb8ba00f 100644 --- a/src/qe/qe_mbp.h +++ b/src/qe/qe_mbp.h @@ -31,17 +31,33 @@ namespace qe { struct cant_project {}; + struct def { + expr_ref var, term; + def(const expr_ref& v, expr_ref& t): var(v), term(t) {} + }; + class project_plugin { public: virtual ~project_plugin() {} virtual bool operator()(model& model, app* var, app_ref_vector& vars, expr_ref_vector& lits) = 0; + /** + \brief partial solver. + */ virtual bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) = 0; virtual family_id get_family_id() = 0; + virtual void operator()(model& model, app_ref_vector& vars, expr_ref_vector& lits) { }; + /** + \brief project vars modulo model, return set of definitions for eliminated variables. + - vars in/out: returns variables that were not eliminated + - lits in/out: returns projected literals + - returns set of definitions + (TBD: in triangular form, the last definition can be substituted into definitions that come before) + */ + virtual vector project(model& model, app_ref_vector& vars, expr_ref_vector& lits) = 0; + static expr_ref pick_equality(ast_manager& m, model& model, expr* t); - static void partition_values(model& model, expr_ref_vector const& vals, expr_ref_vector& lits); - static void partition_args(model& model, app_ref_vector const& sels, expr_ref_vector& lits); static void erase(expr_ref_vector& lits, unsigned& i); static void push_back(expr_ref_vector& lits, expr* lit); static void mark_rec(expr_mark& visited, expr* e); @@ -52,14 +68,18 @@ namespace qe { class impl; impl * m_impl; public: - mbp(ast_manager& m); - + mbp(ast_manager& m, params_ref const& p = params_ref()); + ~mbp(); - + + void updt_params(params_ref const& p); + + static void get_param_descrs(param_descrs & r); + /** \brief - Apply model-based qe on constants provided as vector of variables. - Return the updated formula and updated set of variables that were not eliminated. + Apply model-based qe on constants provided as vector of variables. + Return the updated formula and updated set of variables that were not eliminated. */ void operator()(bool force_elim, app_ref_vector& vars, model& mdl, expr_ref_vector& fmls); @@ -70,17 +90,26 @@ namespace qe { void solve(model& model, app_ref_vector& vars, expr_ref_vector& lits); /** - \brief + \brief Extract literals from formulas based on model. */ void extract_literals(model& model, expr_ref_vector& lits); /** - \brief + \brief Maximize objective t under current model for constraints in fmls. */ opt::inf_eps maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt); + + /** + \brief + Apply spacer friendly MBP. + Use parameters to control behavior. + - reduce_all_selects (false) + - dont_sub (false) + */ + void spacer(app_ref_vector& vars, model& mdl, expr_ref& fml); }; } -#endif +#endif diff --git a/src/qe/qe_solve_plugin.cpp b/src/qe/qe_solve_plugin.cpp new file mode 100644 index 000000000..1f314848f --- /dev/null +++ b/src/qe/qe_solve_plugin.cpp @@ -0,0 +1,401 @@ +/** +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + qe_solve.cpp + +Abstract: + + Light-weight variable solving plugin model for qe-lite and term_graph. + +Author: + + Nikolaj Bjorner (nbjorner), Arie Gurfinkel 2018-6-8 + +Revision History: + + +--*/ + +#include "ast/arith_decl_plugin.h" +#include "ast/datatype_decl_plugin.h" +#include "ast/ast_util.h" +#include "ast/ast_pp.h" +#include "qe/qe_solve_plugin.h" + +namespace qe { + + expr_ref solve_plugin::operator()(expr* lit) { + if (m.is_not(lit, lit)) + return solve(lit, false); + else + return solve(lit, true); + } + + class arith_solve_plugin : public solve_plugin { + arith_util a; + public: + arith_solve_plugin(ast_manager& m, is_variable_proc& is_var): solve_plugin(m, m.get_family_id("arith"), is_var), a(m) {} + + typedef std::pair signed_expr; + + /** + *\brief + * return r * (sum_{(sign,e) \in exprs} sign * e) + */ + expr_ref mk_term(bool is_int, rational const& r, bool sign, svector const& exprs) { + expr_ref_vector result(m); + for (auto const& kv : exprs) { + bool sign2 = kv.first; + expr* e = kv.second; + rational r2(r); + if (sign == sign2) { + r2.neg(); + } + if (!r2.is_one()) { + result.push_back(a.mk_mul(a.mk_numeral(r2, is_int), e)); + } + else { + result.push_back(e); + } + } + return a.mk_add_simplify(result); + } + + /** + * \brief return true if lhs = rhs can be solved as v = t, where v is a variable. + */ + bool solve(expr* lhs, expr* rhs, expr_ref& v, expr_ref& t) { + if (!a.is_int(lhs) && !a.is_real(rhs)) { + return false; + } + rational a_val; + bool is_int = a.is_int(lhs); + svector todo, done; + todo.push_back(std::make_pair(true, lhs)); + todo.push_back(std::make_pair(false, rhs)); + while (!todo.empty()) { + expr* e = todo.back().second; + bool sign = todo.back().first; + todo.pop_back(); + if (a.is_add(e)) { + for (expr* arg : *to_app(e)) { + todo.push_back(std::make_pair(sign, arg)); + } + } + else if (a.is_sub(e)) { + app* sub = to_app(e); + todo.push_back(std::make_pair(sign, sub->get_arg(0))); + for (unsigned i = 1; i < sub->get_num_args(); ++i) { + todo.push_back(std::make_pair(!sign, sub->get_arg(i))); + } + } + else if (a.is_uminus(e)) { + todo.push_back(std::make_pair(!sign, to_app(e)->get_arg(0))); + } + else if (is_invertible_mul(is_int, e, a_val)) { + done.append(todo); + v = e; + a_val = rational(1)/a_val; + t = mk_term(is_int, a_val, sign, done); + TRACE("qe", tout << mk_pp(lhs, m) << " " << mk_pp(rhs, m) << " " << e << " := " << t << "\n";); + return true; + } + else { + done.push_back(std::make_pair(sign, e)); + } + } + return false; + } + + // is x a constant and can we safely divide by x to solve for a variable? + bool is_invertible_const(bool is_int, expr* x, rational& a_val) { + expr* y; + if (a.is_uminus(x, y) && is_invertible_const(is_int, y, a_val)) { + a_val.neg(); + return true; + } + else if (a.is_numeral(x, a_val) && !a_val.is_zero()) { + if (!is_int || a_val.is_one() || a_val.is_minus_one()) { + return true; + } + } + return false; + } + + // is arg of the form a_val * v, where a_val + // is a constant that we can safely divide by. + bool is_invertible_mul(bool is_int, expr*& arg, rational& a_val) { + if (is_variable(arg)) { + a_val = rational(1); + return true; + } + expr* x, *y; + if (a.is_mul(arg, x, y)) { + if (is_variable(x) && is_invertible_const(is_int, y, a_val)) { + arg = x; + return true; + } + if (is_variable(y) && is_invertible_const(is_int, x, a_val)) { + arg = y; + return true; + } + } + return false; + } + + + expr_ref mk_eq_core (expr *e1, expr *e2) { + expr_ref v(m), t(m); + if (solve(e1, e2, v, t)) { + return expr_ref(m.mk_eq(v, t), m); + } + + if (a.is_zero(e1)) { + std::swap(e1, e2); + } + // y + -1*x == 0 --> y = x + expr *a0 = 0, *a1 = 0, *x = 0; + if (a.is_zero(e2) && a.is_add(e1, a0, a1)) { + if (a.is_times_minus_one(a1, x)) { + e1 = a0; + e2 = x; + } + else if (a.is_times_minus_one(a0, x)) { + e1 = a1; + e2 = x; + } + } + return expr_ref(m.mk_eq(e1, e2), m); + } + + app* mk_le_zero(expr *arg) { + expr *e1, *e2, *e3; + if (a.is_add(arg, e1, e2)) { + // e1-e2<=0 --> e1<=e2 + if (a.is_times_minus_one(e2, e3)) { + return a.mk_le(e1, e3); + } + // -e1+e2<=0 --> e2<=e1 + else if (a.is_times_minus_one(e1, e3)) { + return a.mk_le(e2, e3); + } + } + return a.mk_le(arg, mk_zero()); + } + + app* mk_ge_zero(expr *arg) { + expr *e1, *e2, *e3; + if (a.is_add(arg, e1, e2)) { + // e1-e2>=0 --> e1>=e2 + if (a.is_times_minus_one(e2, e3)) { + return a.mk_ge(e1, e3); + } + // -e1+e2>=0 --> e2>=e1 + else if (a.is_times_minus_one(e1, e3)) { + return a.mk_ge(e2, e3); + } + } + return a.mk_ge(arg, mk_zero()); + } + + bool mk_le_core (expr *arg1, expr * arg2, expr_ref &result) { + // t <= -1 ==> t < 0 ==> ! (t >= 0) + rational n; + if (a.is_int (arg1) && a.is_minus_one (arg2)) { + result = m.mk_not (mk_ge_zero (arg1)); + return true; + } + else if (a.is_zero(arg2)) { + result = mk_le_zero(arg1); + return true; + } + else if (a.is_int(arg1) && a.is_numeral(arg2, n) && n < 0) { + // t <= n ==> t < n + 1 ==> ! (t >= n + 1) + result = m.mk_not(a.mk_ge(arg1, a.mk_numeral(n+1, true))); + return true; + } + return false; + } + + expr * mk_zero () { + return a.mk_numeral (rational (0), true); + } + + bool is_one (expr const * n) const { + rational val; + return a.is_numeral (n, val) && val.is_one (); + } + + bool mk_ge_core (expr * arg1, expr * arg2, expr_ref &result) { + // t >= 1 ==> t > 0 ==> ! (t <= 0) + rational n; + if (a.is_int (arg1) && is_one (arg2)) { + result = m.mk_not (mk_le_zero (arg1)); + return true; + } + else if (a.is_zero(arg2)) { + result = mk_ge_zero(arg1); + return true; + } + else if (a.is_int(arg1) && a.is_numeral(arg2, n) && n > 0) { + // t >= n ==> t > n - 1 ==> ! (t <= n - 1) + result = m.mk_not(a.mk_le(arg1, a.mk_numeral(n-1, true))); + return true; + } + return false; + } + + expr_ref solve(expr* atom, bool is_pos) override { + expr *e1, *e2; + + expr_ref res(atom, m); + if (m.is_eq (atom, e1, e2)) { + expr_ref v(m), t(m); + v = e1; t = e2; + // -- attempt to solve using arithmetic + solve(e1, e2, v, t); + // -- normalize equality + res = mk_eq_core(v, t); + } + else if (a.is_le(atom, e1, e2)) { + mk_le_core(e1, e2, res); + } + else if (a.is_ge(atom, e1, e2)) { + mk_ge_core(e1, e2, res); + } + + // restore negation + if (!is_pos) { + res = mk_not(m, res); + } + return res; + } + }; + + class basic_solve_plugin : public solve_plugin { + public: + basic_solve_plugin(ast_manager& m, is_variable_proc& is_var): + solve_plugin(m, m.get_basic_family_id(), is_var) {} + + expr_ref solve(expr *atom, bool is_pos) override { + expr_ref res(atom, m); + expr* lhs = nullptr, *rhs = nullptr, *n = nullptr; + if (m.is_eq(atom, lhs, rhs)) { + if (m.is_not(lhs, n) && is_variable(n)) { + res = m.mk_eq(n, mk_not(m, rhs)); + } + else if (m.is_not(rhs, n) && is_variable(n)) { + res = m.mk_eq(n, mk_not(m, lhs)); + } + else if (is_variable(rhs) && !is_variable(lhs)) { + res = m.mk_eq(rhs, lhs); + } + } + // (ite cond (= VAR t) (= VAR t2)) case + expr* cond = nullptr, *th = nullptr, *el = nullptr; + if (m.is_ite(atom, cond, th, el)) { + expr_ref r1 = solve(th, true); + expr_ref r2 = solve(el, true); + expr* v1 = nullptr, *t1 = nullptr, *v2 = nullptr, *t2 = nullptr; + if (m.is_eq(r1, v1, t1) && m.is_eq(r2, v2, t2) && v1 == v2) { + res = m.mk_eq(v1, m.mk_ite(cond, t1, t2)); + } + } + + if (is_variable(atom) && m.is_bool(atom)) { + res = m.mk_eq(atom, m.mk_bool_val(is_pos)); + return res; + } + + return is_pos ? res : mk_not(res); + } + }; + + class dt_solve_plugin : public solve_plugin { + datatype_util dt; + public: + dt_solve_plugin(ast_manager& m, is_variable_proc& is_var): + solve_plugin(m, m.get_family_id("datatype"), is_var), + dt(m) {} + + expr_ref solve(expr *atom, bool is_pos) override { + expr_ref res(atom, m); + expr* lhs = nullptr, *rhs = nullptr; + if (m.is_eq(atom, lhs, rhs)) { + if (dt.is_constructor(rhs)) { + std::swap(lhs, rhs); + } + if (dt.is_constructor(lhs) && dt.is_constructor(rhs)) { + app* l = to_app(lhs), *r = to_app(rhs); + if (l->get_decl() == r->get_decl()) { + expr_ref_vector eqs(m); + for (unsigned i = 0, sz = l->get_num_args(); i < sz; ++i) { + eqs.push_back(m.mk_eq(l->get_arg(i), r->get_arg(i))); + } + res = mk_and(eqs); + } + else { + res = m.mk_false(); + } + } + else if (dt.is_constructor(lhs)) { + app* l = to_app(lhs); + func_decl* d = l->get_decl(); + expr_ref_vector conjs(m); + conjs.push_back(dt.mk_is(d, rhs)); + ptr_vector const& acc = *dt.get_constructor_accessors(d); + for (unsigned i = 0; i < acc.size(); ++i) { + conjs.push_back(m.mk_eq(l->get_arg(i), m.mk_app(acc[i], rhs))); + } + res = mk_and(conjs); + } + } + // TBD: can also solve for is_nil(x) by x = nil + // + return is_pos ? res : mk_not(res); + } + }; + + class bv_solve_plugin : public solve_plugin { + public: + bv_solve_plugin(ast_manager& m, is_variable_proc& is_var): solve_plugin(m, m.get_family_id("bv"), is_var) {} + expr_ref solve(expr *atom, bool is_pos) override { + expr_ref res(atom, m); + return is_pos ? res : mk_not(res); + } + }; + + class array_solve_plugin : public solve_plugin { + public: + array_solve_plugin(ast_manager& m, is_variable_proc& is_var): solve_plugin(m, m.get_family_id("array"), is_var) {} + expr_ref solve(expr *atom, bool is_pos) override { + expr_ref res(atom, m); + return is_pos ? res : mk_not(res); + } + }; + + solve_plugin* mk_basic_solve_plugin(ast_manager& m, is_variable_proc& is_var) { + return alloc(basic_solve_plugin, m, is_var); + } + + solve_plugin* mk_arith_solve_plugin(ast_manager& m, is_variable_proc& is_var) { + return alloc(arith_solve_plugin, m, is_var); + } + + solve_plugin* mk_dt_solve_plugin(ast_manager& m, is_variable_proc& is_var) { + return alloc(dt_solve_plugin, m, is_var); + } + +#if 0 + solve_plugin* mk_bv_solve_plugin(ast_manager& m, is_variable_proc& is_var) { + return alloc(bv_solve_plugin, m, is_var); + } + + solve_plugin* mk_array_solve_plugin(ast_manager& m, is_variable_proc& is_var) { + return alloc(array_solve_plugin, m, is_var); + } +#endif + +} diff --git a/src/qe/qe_solve_plugin.h b/src/qe/qe_solve_plugin.h new file mode 100644 index 000000000..571d568f1 --- /dev/null +++ b/src/qe/qe_solve_plugin.h @@ -0,0 +1,54 @@ +/** +Copyright (c) 2018 Microsoft Corporation + +Module Name: + + qe_solve.h + +Abstract: + + Light-weight variable solving plugin model for qe-lite and term_graph. + +Author: + + Nikolaj Bjorner (nbjorner), Arie Gurfinkel 2018-6-8 + +Revision History: + + +--*/ + +#pragma once + +#include "ast/ast.h" +#include "util/plugin_manager.h" +#include "qe/qe_vartest.h" + +namespace qe { + + class solve_plugin { + protected: + ast_manager& m; + family_id m_id; + is_variable_proc& m_is_var; + + virtual expr_ref solve(expr* atom, bool is_pos) = 0; + bool is_variable(expr* e) const { return m_is_var(e); } + public: + solve_plugin(ast_manager& m, family_id fid, is_variable_proc& is_var) : m(m), m_id(fid), m_is_var(is_var) {} + virtual ~solve_plugin() {} + family_id get_family_id() const { return m_id; } + /// Process (and potentially augment) a literal + expr_ref operator() (expr *lit); + }; + + solve_plugin* mk_basic_solve_plugin(ast_manager& m, is_variable_proc& is_var); + + solve_plugin* mk_arith_solve_plugin(ast_manager& m, is_variable_proc& is_var); + + solve_plugin* mk_dt_solve_plugin(ast_manager& m, is_variable_proc& is_var); + + // solve_plugin* mk_bv_solve_plugin(ast_manager& m, is_variable_proc& is_var); + + // solve_plugin* mk_array_solve_plugin(ast_manager& m, is_variable_proc& is_var); +} diff --git a/src/qe/qe_term_graph.cpp b/src/qe/qe_term_graph.cpp new file mode 100644 index 000000000..510868366 --- /dev/null +++ b/src/qe/qe_term_graph.cpp @@ -0,0 +1,873 @@ +/**++ +Copyright (c) Arie Gurfinkel + +Module Name: + + qe_term_graph.cpp + +Abstract: + + Equivalence graph of terms + +Author: + + Arie Gurfinkel + +Notes: + +--*/ + +#include "util/util.h" +#include "util/uint_set.h" +#include "ast/ast_pp.h" +#include "ast/ast_util.h" +#include "ast/for_each_expr.h" +#include "ast/occurs.h" +#include "qe/qe_term_graph.h" +#include "model/model_evaluator.h" + +namespace qe { + + static expr_ref mk_neq(ast_manager &m, expr *e1, expr *e2) { + expr *t = nullptr; + // x != !x == true + if ((m.is_not(e1, t) && t == e2) || (m.is_not(e2, t) && t == e1)) + return expr_ref(m.mk_true(), m); + else if (m.are_distinct(e1, e2)) + return expr_ref(m.mk_true(), m); + return expr_ref(m.mk_not(m.mk_eq(e1, e2)), m); + } + + namespace { + struct sort_lt_proc { + bool operator()(const expr* a, const expr *b) const { + return get_sort(a)->get_id() < get_sort(b)->get_id(); + } + }; + } + + namespace is_pure_ns { + struct found{}; + struct proc { + is_variable_proc &m_is_var; + proc(is_variable_proc &is_var) : m_is_var(is_var) {} + void operator()(var *n) const {if (m_is_var(n)) throw found();} + void operator()(app const *n) const {if (m_is_var(n)) throw found();} + void operator()(quantifier *n) const {} + }; + } + + bool is_pure(is_variable_proc &is_var, expr *e) { + try { + is_pure_ns::proc v(is_var); + quick_for_each_expr(v, e); + } + catch (is_pure_ns::found) { + return false; + } + return true; + } + + class term { + // -- an app represented by this term + expr_ref m_expr; // NSB: to make usable with exprs + // -- root of the equivalence class + term* m_root; + // -- next element in the equivalence class (cyclic linked list) + term* m_next; + // -- eq class size + unsigned m_class_size; + // -- general purpose mark + unsigned m_mark:1; + // -- general purpose second mark + unsigned m_mark2:1; + // -- is an interpreted constant + unsigned m_interpreted:1; + + // -- terms that contain this term as a child + ptr_vector m_parents; + + // arguments of term. + ptr_vector m_children; + + public: + term(expr_ref const& v, u_map& app2term) : + m_expr(v), + m_root(this), + m_next(this), + m_class_size(1), + m_mark(false), + m_mark2(false), + m_interpreted(false) { + if (!is_app()) return; + for (expr* e : *to_app(m_expr)) { + term* t = app2term[e->get_id()]; + t->get_root().m_parents.push_back(this); + m_children.push_back(t); + } + } + + ~term() {} + + class parents { + term const& t; + public: + parents(term const& _t):t(_t) {} + parents(term const* _t):t(*_t) {} + ptr_vector::const_iterator begin() const { return t.m_parents.begin(); } + ptr_vector::const_iterator end() const { return t.m_parents.end(); } + }; + + class children { + term const& t; + public: + children(term const& _t):t(_t) {} + children(term const* _t):t(*_t) {} + ptr_vector::const_iterator begin() const { return t.m_children.begin(); } + ptr_vector::const_iterator end() const { return t.m_children.end(); } + }; + + // Congruence table hash function is based on + // roots of children and function declaration. + + unsigned get_hash() const { + unsigned a, b, c; + a = b = c = get_decl_id(); + for (term * ch : children(this)) { + a = ch->get_root().get_id(); + mix(a, b, c); + } + return c; + } + + static bool cg_eq(term const * t1, term const * t2) { + if (t1->get_decl_id() != t2->get_decl_id()) return false; + if (t1->m_children.size() != t2->m_children.size()) return false; + for (unsigned i = 0, sz = t1->m_children.size(); i < sz; ++ i) { + if (t1->m_children[i]->get_root().get_id() != t2->m_children[i]->get_root().get_id()) return false; + } + return true; + } + + unsigned get_id() const { return m_expr->get_id();} + + unsigned get_decl_id() const { return is_app() ? get_app()->get_decl()->get_id() : m_expr->get_id(); } + + bool is_marked() const {return m_mark;} + void set_mark(bool v){m_mark = v;} + bool is_marked2() const {return m_mark2;} // NSB: where is this used? + void set_mark2(bool v){m_mark2 = v;} // NSB: where is this used? + + bool is_interpreted() const {return m_interpreted;} + bool is_theory() const { return !is_app() || get_app()->get_family_id() != null_family_id; } + void mark_as_interpreted() {m_interpreted=true;} + expr* get_expr() const {return m_expr;} + bool is_app() const {return ::is_app(m_expr);} + app *get_app() const {return is_app() ? to_app(m_expr) : nullptr;} + unsigned get_num_args() const { return is_app() ? get_app()->get_num_args() : 0; } + + term &get_root() const {return *m_root;} + bool is_root() const {return m_root == this;} + void set_root(term &r) {m_root = &r;} + term &get_next() const {return *m_next;} + void add_parent(term* p) { m_parents.push_back(p); } + + unsigned get_class_size() const {return m_class_size;} + + void merge_eq_class(term &b) { + std::swap(this->m_next, b.m_next); + m_class_size += b.get_class_size(); + // -- reset (useful for debugging) + b.m_class_size = 0; + } + + // -- make this term the root of its equivalence class + void mk_root() { + if (is_root()) return; + + term *curr = this; + do { + if (curr->is_root()) { + // found previous root + SASSERT(curr != this); + m_class_size = curr->get_class_size(); + curr->m_class_size = 0; + } + curr->set_root(*this); + curr = &curr->get_next(); + } + while (curr != this); + } + }; + + + bool term_graph::is_variable_proc::operator()(const expr * e) const { + if (!is_app(e)) return false; + const app *a = ::to_app(e); + return + a->get_family_id() == null_family_id && + !m_solved.contains(a->get_decl()) && + m_exclude == m_decls.contains(a->get_decl()); + } + + bool term_graph::is_variable_proc::operator()(const term &t) const { + return (*this)(t.get_app()); + } + + void term_graph::is_variable_proc::set_decls(const func_decl_ref_vector &decls, bool exclude) { + reset(); + m_exclude = exclude; + for (auto *d : decls) m_decls.insert(d); + } + void term_graph::is_variable_proc::mark_solved(const expr *e) { + if ((*this)(e) && is_app(e)) + m_solved.insert(::to_app(e)->get_decl()); + } + + + unsigned term_graph::term_hash::operator()(term const* t) const { return t->get_hash(); } + + bool term_graph::term_eq::operator()(term const* a, term const* b) const { return term::cg_eq(a, b); } + + term_graph::term_graph(ast_manager &man) : m(man), m_lits(m), m_pinned(m) { + m_plugins.register_plugin(mk_basic_solve_plugin(m, m_is_var)); + m_plugins.register_plugin(mk_arith_solve_plugin(m, m_is_var)); + } + + term_graph::~term_graph() { + reset(); + } + + bool term_graph::is_pure_def(expr *atom, expr*& v) { + expr *e = nullptr; + return m.is_eq(atom, v, e) && m_is_var(v) && is_pure(m_is_var, e); + } + + static family_id get_family_id(ast_manager &m, expr *lit) { + if (m.is_not(lit, lit)) + return get_family_id(m, lit); + + expr *a = nullptr, *b = nullptr; + // deal with equality using sort of range + if (m.is_eq (lit, a, b)) { + return get_sort (a)->get_family_id(); + } + // extract family_id of top level app + else if (is_app(lit)) { + return to_app(lit)->get_decl()->get_family_id(); + } + else { + return null_family_id; + } + } + void term_graph::add_lit(expr *l) { + expr_ref lit(m); + expr_ref_vector lits(m); + lits.push_back(l); + for (unsigned i = 0; i < lits.size(); ++i) { + l = lits.get(i); + family_id fid = get_family_id(m, l); + qe::solve_plugin *pin = m_plugins.get_plugin(fid); + lit = pin ? (*pin)(l) : l; + if (m.is_and(lit)) { + lits.append(::to_app(lit)->get_num_args(), ::to_app(lit)->get_args()); + } + else { + m_lits.push_back(lit); + internalize_lit(lit); + } + } + } + + bool term_graph::is_internalized(expr *a) { + return m_app2term.contains(a->get_id()); + } + + term* term_graph::get_term(expr *a) { + term *res; + return m_app2term.find (a->get_id(), res) ? res : nullptr; + } + + term *term_graph::mk_term(expr *a) { + expr_ref e(a, m); + term * t = alloc(term, e, m_app2term); + if (t->get_num_args() == 0 && m.is_unique_value(a)){ + t->mark_as_interpreted(); + } + + m_terms.push_back(t); + m_app2term.insert(a->get_id(), t); + return t; + } + + term* term_graph::internalize_term(expr *t) { + term* res = get_term(t); + if (res) return res; + ptr_buffer todo; + todo.push_back(t); + while (!todo.empty()) { + t = todo.back(); + res = get_term(t); + if (res) { + todo.pop_back(); + continue; + } + unsigned sz = todo.size(); + if (is_app(t)) { + for (expr * arg : *::to_app(t)) { + if (!get_term(arg)) + todo.push_back(arg); + } + } + if (sz < todo.size()) continue; + todo.pop_back(); + res = mk_term(t); + } + SASSERT(res); + return res; + } + + void term_graph::internalize_eq(expr *a1, expr* a2) { + SASSERT(m_merge.empty()); + merge(*internalize_term(a1), *internalize_term(a2)); + merge_flush(); + SASSERT(m_merge.empty()); + } + + void term_graph::internalize_lit(expr* lit) { + expr *e1 = nullptr, *e2 = nullptr, *v = nullptr; + if (m.is_eq (lit, e1, e2)) { + internalize_eq (e1, e2); + } + else { + internalize_term(lit); + } + if (is_pure_def(lit, v)) { + m_is_var.mark_solved(v); + } + } + + void term_graph::merge_flush() { + while (!m_merge.empty()) { + term* t1 = m_merge.back().first; + term* t2 = m_merge.back().second; + m_merge.pop_back(); + merge(*t1, *t2); + } + } + + void term_graph::merge(term &t1, term &t2) { + // -- merge might invalidate term2app cache + m_term2app.reset(); + m_pinned.reset(); + + term *a = &t1.get_root(); + term *b = &t2.get_root(); + + if (a == b) return; + + if (a->get_class_size() > b->get_class_size()) { + std::swap(a, b); + } + + // Remove parents of it from the cg table. + for (term* p : term::parents(b)) { + if (!p->is_marked()) { + p->set_mark(true); + m_cg_table.erase(p); + } + } + // make 'a' be the root of the equivalence class of 'b' + b->set_root(*a); + for (term *it = &b->get_next(); it != b; it = &it->get_next()) { + it->set_root(*a); + } + + // merge equivalence classes + a->merge_eq_class(*b); + + // Insert parents of b's old equilvalence class into the cg table + for (term* p : term::parents(a)) { + if (p->is_marked()) { + term* p_old = m_cg_table.insert_if_not_there(p); + p->set_mark(false); + a->add_parent(p); + // propagate new equalities. + if (p->get_root().get_id() != p_old->get_root().get_id()) { + m_merge.push_back(std::make_pair(p, p_old)); + } + } + } + } + + expr* term_graph::mk_app_core (expr *e) { + if (is_app(e)) { + expr_ref_buffer kids(m); + app* a = ::to_app(e); + for (expr * arg : *a) { + kids.push_back (mk_app(arg)); + } + app* res = m.mk_app(a->get_decl(), a->get_num_args(), kids.c_ptr()); + m_pinned.push_back(res); + return res; + } + else { + return e; + } + } + + expr_ref term_graph::mk_app(term const &r) { + SASSERT(r.is_root()); + + if (r.get_num_args() == 0) { + return expr_ref(r.get_expr(), m); + } + + expr* res = nullptr; + if (m_term2app.find(r.get_id(), res)) { + return expr_ref(res, m); + } + + res = mk_app_core (r.get_app()); + m_term2app.insert(r.get_id(), res); + return expr_ref(res, m); + + } + + expr_ref term_graph::mk_app(expr *a) { + term *t = get_term(a); + if (!t) + return expr_ref(a, m); + else + return mk_app(t->get_root()); + + } + + void term_graph::mk_equalities(term const &t, expr_ref_vector &out) { + SASSERT(t.is_root()); + expr_ref rep(mk_app(t), m); + for (term *it = &t.get_next(); it != &t; it = &it->get_next()) { + expr* mem = mk_app_core(it->get_app()); + out.push_back (m.mk_eq (rep, mem)); + } + } + + void term_graph::mk_all_equalities(term const &t, expr_ref_vector &out) { + mk_equalities(t, out); + + for (term *it = &t.get_next(); it != &t; it = &it->get_next ()) { + expr* a1 = mk_app_core (it->get_app()); + for (term *it2 = &it->get_next(); it2 != &t; it2 = &it2->get_next()) { + expr* a2 = mk_app_core(it2->get_app()); + out.push_back (m.mk_eq (a1, a2)); + } + } + } + + void term_graph::reset_marks() { + for (term * t : m_terms) { + t->set_mark(false); + } + } + + /// Order of preference for roots of equivalence classes + /// XXX This should be factored out to let clients control the preference + bool term_graph::term_lt(term const &t1, term const &t2) { + + // prefer constants over applications + // prefer uninterpreted constants over values + // prefer smaller expressions over larger ones + if (t1.get_num_args() == 0 || t2.get_num_args() == 0) { + if (t1.get_num_args() == t2.get_num_args()) { + // t1.get_num_args() == t2.get_num_args() == 0 + if (m.is_value(t1.get_expr()) == m.is_value(t2.get_expr())) + return t1.get_id() < t2.get_id(); + return m.is_value(t2.get_expr()); + } + return t1.get_num_args() < t2.get_num_args(); + } + + unsigned sz1 = get_num_exprs(t1.get_expr()); + unsigned sz2 = get_num_exprs(t1.get_expr()); + return sz1 < sz2; + } + + void term_graph::pick_root (term &t) { + term *r = &t; + for (term *it = &t.get_next(); it != &t; it = &it->get_next()) { + it->set_mark(true); + if (term_lt(*it, *r)) { r = it; } + } + + // -- if found something better, make it the new root + if (r != &t) { + r->mk_root(); + } + } + + /// Choose better roots for equivalence classes + void term_graph::pick_roots() { + for (term* t : m_terms) { + if (!t->is_marked() && t->is_root()) + pick_root(*t); + } + reset_marks(); + } + + void term_graph::display(std::ostream &out) { + for (term * t : m_terms) { + out << mk_pp(t->get_expr(), m) << " is root " << t->is_root() + << " cls sz " << t->get_class_size() + << " term " << t + << "\n"; + } + } + + void term_graph::to_lits (expr_ref_vector &lits, bool all_equalities) { + pick_roots(); + + for (expr * a : m_lits) { + if (is_internalized(a)) { + lits.push_back (::to_app(mk_app(a))); + } + } + + for (term * t : m_terms) { + if (!t->is_root()) + continue; + else if (all_equalities) + mk_all_equalities (*t, lits); + else + mk_equalities(*t, lits); + } + } + + expr_ref term_graph::to_app() { + expr_ref_vector lits(m); + to_lits(lits); + return mk_and(lits); + } + + void term_graph::reset() { + m_term2app.reset(); + m_pinned.reset(); + m_app2term.reset(); + std::for_each(m_terms.begin(), m_terms.end(), delete_proc()); + m_terms.reset(); + m_lits.reset(); + m_cg_table.reset(); + } + + class term_graph::projector { + term_graph &m_tg; + ast_manager &m; + u_map m_term2app; + u_map m_root2rep; + + model_ref m_model; + expr_ref_vector m_pinned; // tracks expr in the maps + + expr* mk_pure(term const& t) { + expr* e = nullptr; + if (m_term2app.find(t.get_id(), e)) return e; + e = t.get_expr(); + if (!is_app(e)) return nullptr; + app* a = ::to_app(e); + expr_ref_buffer kids(m); + for (term* ch : term::children(t)) { + if (!m_root2rep.find(ch->get_root().get_id(), e)) return nullptr; + kids.push_back(e); + } + expr* pure = m.mk_app(a->get_decl(), kids.size(), kids.c_ptr()); + m_pinned.push_back(pure); + m_term2app.insert(t.get_id(), pure); + return pure; + } + + + bool is_better_rep(expr *t1, expr *t2) { + if (!t2) return t1 != nullptr; + return m.is_unique_value(t1) && !m.is_unique_value(t2); + } + + void purify() { + // - propagate representatives up over parents. + // use work-list + marking to propagate. + // - produce equalities over represented classes. + // - produce other literals over represented classes + // (walk disequalities in m_lits and represent + // lhs/rhs over decls or excluding decls) + + ptr_vector worklist; + for (term * t : m_tg.m_terms) { + worklist.push_back(t); + t->set_mark(true); + } + + while (!worklist.empty()) { + term* t = worklist.back(); + worklist.pop_back(); + t->set_mark(false); + if (m_term2app.contains(t->get_id())) + continue; + if (!t->is_theory() && is_projected(*t)) + continue; + + expr* pure = mk_pure(*t); + if (!pure) continue; + + m_term2app.insert(t->get_id(), pure); + expr* rep = nullptr; + // ensure that the root has a representative + m_root2rep.find(t->get_root().get_id(), rep); + + // update rep with pure if it is better + if (pure != rep && is_better_rep(pure, rep)) { + m_root2rep.insert(t->get_root().get_id(), pure); + for (term * p : term::parents(t->get_root())) { + m_term2app.remove(p->get_id()); + if (!p->is_marked()) { + p->set_mark(true); + worklist.push_back(p); + } + } + } + } + + // Here we could also walk equivalence classes that + // contain interpreted values by sort and extract + // disequalities bewteen non-unique value + // representatives. these disequalities are implied + // and can be mined using other means, such as theory + // aware core minimization + m_tg.reset_marks(); + } + + void solve_core() { + ptr_vector worklist; + for (term * t : m_tg.m_terms) { + // skip pure terms + if (m_term2app.contains(t->get_id())) continue; + worklist.push_back(t); + t->set_mark(true); + } + + while (!worklist.empty()) { + term* t = worklist.back(); + worklist.pop_back(); + t->set_mark(false); + if (m_term2app.contains(t->get_id())) + continue; + + expr* pure = mk_pure(*t); + if (!pure) continue; + + m_term2app.insert(t->get_id(), pure); + expr* rep = nullptr; + // ensure that the root has a representative + m_root2rep.find(t->get_root().get_id(), rep); + + if (!rep) { + m_root2rep.insert(t->get_root().get_id(), pure); + for (term * p : term::parents(t->get_root())) { + SASSERT(!m_term2app.contains(p->get_id())); + if (!p->is_marked()) { + p->set_mark(true); + worklist.push_back(p); + } + } + } + } + m_tg.reset_marks(); + } + + bool find_app(term &t, expr *&res) { + return m_root2rep.find(t.get_root().get_id(), res); + } + + bool find_app(expr *lit, expr *&res) { + return m_root2rep.find(m_tg.get_term(lit)->get_root().get_id(), res); + } + + void mk_lits(expr_ref_vector &res) { + expr *e = nullptr; + for (auto *lit : m_tg.m_lits) { + if (!m.is_eq(lit) && find_app(lit, e)) + res.push_back(e); + } + } + + void mk_pure_equalities(const term &t, expr_ref_vector &res) { + SASSERT(t.is_root()); + expr *rep = nullptr; + if (!m_root2rep.find(t.get_id(), rep)) return; + obj_hashtable members; + members.insert(rep); + term const * r = &t; + do { + expr* member = nullptr; + if (m_term2app.find(r->get_id(), member) && !members.contains(member)) { + res.push_back (m.mk_eq (rep, member)); + members.insert(member); + } + r = &r->get_next(); + } + while (r != &t); + } + + bool is_projected(const term &t) {return m_tg.m_is_var(t);} + + void mk_unpure_equalities(const term &t, expr_ref_vector &res) { + expr *rep = nullptr; + if (!m_root2rep.find(t.get_id(), rep)) return; + obj_hashtable members; + members.insert(rep); + term const * r = &t; + do { + expr* member = mk_pure(*r); + SASSERT(member); + if (!members.contains(member) && + (!is_projected(*r) || !is_solved_eq(rep, member))) { + res.push_back(m.mk_eq(rep, member)); + members.insert(member); + } + r = &r->get_next(); + } + while (r != &t); + } + + void mk_equalities(bool pure, expr_ref_vector &res) { + for (term *t : m_tg.m_terms) { + if (!t->is_root()) continue; + if (!m_root2rep.contains(t->get_id())) continue; + if (pure) + mk_pure_equalities(*t, res); + else + mk_unpure_equalities(*t, res); + } + } + + void mk_pure_equalities(expr_ref_vector &res) { + return mk_equalities(true, res); + } + + void mk_unpure_equalities(expr_ref_vector &res) { + return mk_equalities(false, res); + } + + // TBD: generalize for also the case of a (:var n) + bool is_solved_eq(expr *lhs, expr* rhs) { + return is_uninterp_const(rhs) && !occurs(rhs, lhs); + } + + /// Add equalities and disequalities for all pure representatives + /// based on their equivalence in the model + void model_complete(expr_ref_vector &res) { + if (!m_model) return; + obj_map val2rep; + model_evaluator mev(*m_model); + for (auto &kv : m_root2rep) { + expr *rep = kv.m_value; + expr_ref val(m); + expr *u = nullptr; + if (!mev.eval(rep, val)) continue; + if (val2rep.find(val, u)) { + res.push_back(m.mk_eq(u, rep)); + } + else { + val2rep.insert(val, rep); + } + } + + // TBD: optimize further based on implied values (e.g., + // some literals are forced to be true/false) and based on + // unique_values (e.g., (x=1 & y=1) does not require + // (x!=y) to be added + ptr_buffer reps; + for (auto &kv : val2rep) { + expr *rep = kv.m_value; + if (!m.is_unique_value(rep)) + reps.push_back(kv.m_value); + } + + if (reps.size() <= 1) return; + + // -- sort representatives, call mk_distinct on any range + // -- of the same sort longer than 1 + std::sort(reps.c_ptr(), reps.c_ptr() + reps.size(), sort_lt_proc()); + unsigned i = 0; + unsigned sz = reps.size(); + while (i < sz) { + sort* last_sort = get_sort(reps.get(i)); + unsigned j = i + 1; + while (j < sz && last_sort == get_sort(reps.get(j))) {++j;} + if (j - i == 2) { + expr_ref d(m); + d = mk_neq(m, reps.get(i), reps.get(i+1)); + if (!m.is_true(d)) res.push_back(d); + } + else if (j - i > 2) + res.push_back(m.mk_distinct(j - i, reps.c_ptr() + i)); + i = j; + } + TRACE("qe", tout << "after distinct: " << res << "\n";); + } + + public: + projector(term_graph &tg) : m_tg(tg), m(m_tg.m), m_pinned(m) {} + + void set_model(model &mdl) { m_model = &mdl; } + + void reset() { + m_tg.reset_marks(); + m_term2app.reset(); + m_root2rep.reset(); + m_pinned.reset(); + m_model.reset(); + } + expr_ref_vector project() { + expr_ref_vector res(m); + purify(); + mk_lits(res); + mk_pure_equalities(res); + model_complete(res); + reset(); + return res; + } + expr_ref_vector solve() { + expr_ref_vector res(m); + purify(); + solve_core(); + mk_lits(res); + mk_unpure_equalities(res); + reset(); + return res; + } + }; + + void term_graph::set_vars(func_decl_ref_vector const& decls, bool exclude) { + m_is_var.set_decls(decls, exclude); + } + + expr_ref_vector term_graph::project() { + // reset solved vars so that they are not considered pure by projector + m_is_var.reset_solved(); + term_graph::projector p(*this); + return p.project(); + } + + expr_ref_vector term_graph::project(model &mdl) { + m_is_var.reset_solved(); + term_graph::projector p(*this); + p.set_model(mdl); + return p.project(); + } + + expr_ref_vector term_graph::solve() { + // reset solved vars so that they are not considered pure by projector + m_is_var.reset_solved(); + term_graph::projector p(*this); + return p.solve(); + } + +} diff --git a/src/qe/qe_term_graph.h b/src/qe/qe_term_graph.h new file mode 100644 index 000000000..19b694a76 --- /dev/null +++ b/src/qe/qe_term_graph.h @@ -0,0 +1,119 @@ +/**++ +Copyright (c) Arie Gurfinkel + +Module Name: + + qe_term_graph.h + +Abstract: + + Equivalence graph of terms + +Author: + + Arie Gurfinkel + +Notes: + +--*/ +#ifndef QE_TERM_GRAPH_H__ +#define QE_TERM_GRAPH_H__ + +#include "ast/ast.h" +#include "util/plugin_manager.h" +#include "qe/qe_solve_plugin.h" +#include "qe/qe_vartest.h" +#include "model/model.h" + +namespace qe { + + class term; + + class term_graph { + class projector; + + class is_variable_proc : public ::is_variable_proc { + bool m_exclude; + obj_hashtable m_decls, m_solved; + public: + bool operator()(const expr *e) const override; + bool operator()(const term &t) const; + + void set_decls(const func_decl_ref_vector &decls, bool exclude); + void mark_solved(const expr *e); + void reset_solved() {m_solved.reset();} + void reset() {m_decls.reset(); m_solved.reset(); m_exclude = true;} + }; + + struct term_hash { unsigned operator()(term const* t) const; }; + struct term_eq { bool operator()(term const* a, term const* b) const; }; + ast_manager & m; + ptr_vector m_terms; + expr_ref_vector m_lits; // NSB: expr_ref_vector? + u_map m_app2term; + ast_ref_vector m_pinned; + u_map m_term2app; + plugin_manager m_plugins; + ptr_hashtable m_cg_table; + vector> m_merge; + + term_graph::is_variable_proc m_is_var; + void merge(term &t1, term &t2); + void merge_flush(); + + term *mk_term(expr *t); + term *get_term(expr *t); + + term *internalize_term(expr *t); + void internalize_eq(expr *a1, expr *a2); + void internalize_lit(expr *lit); + + bool is_internalized(expr *a); + + bool term_lt(term const &t1, term const &t2); + void pick_root (term &t); + void pick_roots(); + + void reset_marks(); + + expr* mk_app_core(expr* a); + expr_ref mk_app(term const &t); + expr* mk_pure(term& t); + expr_ref mk_app(expr *a); + void mk_equalities(term const &t, expr_ref_vector &out); + void mk_all_equalities(term const &t, expr_ref_vector &out); + void display(std::ostream &out); + + bool is_pure_def(expr* atom, expr *& v); + + public: + term_graph(ast_manager &m); + ~term_graph(); + + void set_vars(func_decl_ref_vector const& decls, bool exclude); + + ast_manager& get_ast_manager() const { return m;} + + void add_lit(expr *lit); + void add_lits(expr_ref_vector const &lits) { for (expr* e : lits) add_lit(e); } + void add_eq(expr* a, expr* b) { internalize_eq(a, b); } + + void reset(); + + // deprecate? + void to_lits(expr_ref_vector &lits, bool all_equalities = false); + expr_ref to_app(); + + /** + * Return literals obtained by projecting added literals + * onto the vocabulary of decls (if exclude is false) or outside the + * vocabulary of decls (if exclude is true). + */ + expr_ref_vector project(); + expr_ref_vector solve(); + expr_ref_vector project(model &mdl); + + }; + +} +#endif diff --git a/src/qe/qe_vartest.h b/src/qe/qe_vartest.h index 56d9229b8..52609893f 100644 --- a/src/qe/qe_vartest.h +++ b/src/qe/qe_vartest.h @@ -22,9 +22,10 @@ Revision History: #include "ast/ast.h" #include "util/uint_set.h" -class is_variable_proc { +// TBD: move under qe namespace +class is_variable_proc : public std::unary_function { public: - virtual bool operator()(expr* e) const = 0; + virtual bool operator()(const expr* e) const = 0; }; class is_variable_test : public is_variable_proc { @@ -42,7 +43,7 @@ public: m_num_decls(num_decls), m_var_kind(BY_NUM_DECLS) {} - bool operator()(expr* e) const override { + bool operator()(const expr* e) const override { if (!is_var(e)) { return false; } diff --git a/src/qe/qsat.cpp b/src/qe/qsat.cpp index 64cde4b1d..a2e8fd8b1 100644 --- a/src/qe/qsat.cpp +++ b/src/qe/qsat.cpp @@ -516,8 +516,8 @@ namespace qe { expr_ref val_a(m), val_b(m); expr* a = it->m_key; expr* b = it->m_value; - VERIFY(model.eval(a, val_a)); - VERIFY(model.eval(b, val_b)); + val_a = model(a); + val_b = model(b); if (val_a != val_b) { TRACE("qe", tout << mk_pp(a, m) << " := " << val_a << "\n"; @@ -1060,11 +1060,9 @@ namespace qe { } bool validate_assumptions(model& mdl, expr_ref_vector const& core) { - for (unsigned i = 0; i < core.size(); ++i) { - expr_ref val(m); - VERIFY(mdl.eval(core[i], val)); - if (!m.is_true(val)) { - TRACE("qe", tout << "component of core is not true: " << mk_pp(core[i], m) << "\n";); + for (expr* c : core) { + if (!mdl.is_true(c)) { + TRACE("qe", tout << "component of core is not true: " << mk_pp(c, m) << "\n";); return false; } } @@ -1111,14 +1109,10 @@ namespace qe { bool validate_model(model& mdl, unsigned sz, expr* const* fmls) { expr_ref val(m); for (unsigned i = 0; i < sz; ++i) { - if (!m_model->eval(fmls[i], val) && !m.canceled()) { - TRACE("qe", tout << "Formula does not evaluate in model: " << mk_pp(fmls[i], m) << "\n";); + if (!m_model->is_true(fmls[i]) && !m.canceled()) { + TRACE("qe", tout << "Formula does not evaluate to true in model: " << mk_pp(fmls[i], m) << "\n";); return false; } - if (!m.is_true(val)) { - TRACE("qe", tout << mk_pp(fmls[i], m) << " evaluates to " << val << " in model\n";); - return false; - } } return true; } diff --git a/src/sat/ba_solver.cpp b/src/sat/ba_solver.cpp index 4fbf7570d..930617301 100644 --- a/src/sat/ba_solver.cpp +++ b/src/sat/ba_solver.cpp @@ -166,10 +166,11 @@ namespace sat { unsigned w = 0; for (unsigned i = 0; i < m_size; ++i) { m_wlits[i].second.neg(); + VERIFY(w + m_wlits[i].first >= w); w += m_wlits[i].first; - } + } m_k = w - m_k + 1; - SASSERT(w >= m_k && m_k > 0); + VERIFY(w >= m_k && m_k > 0); } bool ba_solver::pb::is_watching(literal l) const { @@ -531,7 +532,7 @@ namespace sat { VERIFY(p.lit() == null_literal || value(p.lit()) == l_true); unsigned sz = p.size(), bound = p.k(); - + // put the non-false literals into the head. unsigned slack = 0, slack1 = 0, num_watch = 0, j = 0; for (unsigned i = 0; i < sz; ++i) { @@ -555,7 +556,7 @@ namespace sat { bool is_false = false; for (unsigned k = 0; k < sz; ++k) { SASSERT(!is_false || value(p[k].second) == l_false); - SASSERT(k < j == (value(p[k].second) != l_false)); + SASSERT((k < j) == (value(p[k].second) != l_false)); is_false = value(p[k].second) == l_false; }); @@ -833,9 +834,19 @@ namespace sat { remove_constraint(p, "recompiled to cardinality"); return; } - else { p.set_size(sz); + p.update_max_sum(); + if (p.max_sum() < k) { + if (p.lit() == null_literal) { + s().set_conflict(justification()); + } + else { + s().assign(~p.lit(), justification()); + } + remove_constraint(p, "recompiled to false"); + return; + } p.set_k(k); SASSERT(p.well_formed()); @@ -3210,6 +3221,7 @@ namespace sat { if (is_marked(l) && m_weights[l.index()] <= p2.get_coeff(i)) { ++num_sub; } + if (p1.size() + i > p2.size() + num_sub) return false; } return num_sub == p1.size(); } @@ -3364,8 +3376,9 @@ namespace sat { m_weights.setx(l.second.index(), l.first, 0); mark_visited(l.second); } - for (unsigned i = 0; i < p1.num_watch(); ++i) { - subsumes(p1, p1[i].second); + for (unsigned i = 0; i < std::min(10u, p1.num_watch()); ++i) { + unsigned j = s().m_rand() % p1.num_watch(); + subsumes(p1, p1[j].second); } for (wliteral l : p1) { m_weights[l.second.index()] = 0; diff --git a/src/sat/ba_solver.h b/src/sat/ba_solver.h index 07e7cfd58..e947cee96 100644 --- a/src/sat/ba_solver.h +++ b/src/sat/ba_solver.h @@ -124,8 +124,8 @@ namespace sat { protected: unsigned m_k; public: - pb_base(tag_t t, unsigned id, literal l, unsigned sz, size_t osz, unsigned k): constraint(t, id, l, sz, osz), m_k(k) {} - virtual void set_k(unsigned k) { m_k = k; } + pb_base(tag_t t, unsigned id, literal l, unsigned sz, size_t osz, unsigned k): constraint(t, id, l, sz, osz), m_k(k) { VERIFY(k < 4000000000); } + virtual void set_k(unsigned k) { VERIFY(k < 4000000000); m_k = k; } virtual unsigned get_coeff(unsigned i) const { UNREACHABLE(); return 0; } unsigned k() const { return m_k; } virtual bool well_formed() const; @@ -157,7 +157,6 @@ namespace sat { unsigned m_num_watch; unsigned m_max_sum; wliteral m_wlits[0]; - void update_max_sum(); public: static size_t get_obj_size(unsigned num_lits) { return sizeof(pb) + num_lits * sizeof(wliteral); } pb(unsigned id, literal lit, svector const& wlits, unsigned k); @@ -171,10 +170,11 @@ namespace sat { void set_slack(unsigned s) { m_slack = s; } unsigned num_watch() const { return m_num_watch; } unsigned max_sum() const { return m_max_sum; } + void update_max_sum(); void set_num_watch(unsigned s) { m_num_watch = s; } bool is_cardinality() const; virtual void negate(); - virtual void set_k(unsigned k) { m_k = k; update_max_sum(); } + virtual void set_k(unsigned k) { m_k = k; VERIFY(k < 4000000000); update_max_sum(); } virtual void swap(unsigned i, unsigned j) { std::swap(m_wlits[i], m_wlits[j]); } virtual literal_vector literals() const { literal_vector lits; for (auto wl : *this) lits.push_back(wl.second); return lits; } virtual bool is_watching(literal l) const; diff --git a/src/sat/sat_bdd.cpp b/src/sat/sat_bdd.cpp index 74af4bf5e..bd1745765 100644 --- a/src/sat/sat_bdd.cpp +++ b/src/sat/sat_bdd.cpp @@ -108,7 +108,7 @@ namespace sat { bool bdd_manager::check_result(op_entry*& e1, op_entry const* e2, BDD a, BDD b, BDD c) { if (e1 != e2) { - SASSERT(e2->m_result != -1); + SASSERT(e2->m_result != null_bdd); push_entry(e1); e1 = nullptr; return true; @@ -117,7 +117,7 @@ namespace sat { e1->m_bdd1 = a; e1->m_bdd2 = b; e1->m_op = c; - SASSERT(e1->m_result == -1); + SASSERT(e1->m_result == null_bdd); return false; } } @@ -203,7 +203,7 @@ namespace sat { void * mem = m_alloc.allocate(sizeof(op_entry)); result = new (mem) op_entry(l, r, op); } - result->m_result = -1; + result->m_result = null_bdd; return result; } @@ -667,7 +667,7 @@ namespace sat { r = e2->m_result; } else { - SASSERT(e1->m_result == -1); + SASSERT(e1->m_result == null_bdd); push(mk_quant_rec(l, lo(b), op)); push(mk_quant_rec(l, hi(b), op)); r = make_node(lvl, read(2), read(1)); @@ -782,7 +782,7 @@ namespace sat { ptr_vector to_delete, to_keep; for (auto* e : m_op_cache) { - if (e->m_result != -1) { + if (e->m_result != null_bdd) { to_delete.push_back(e); } else { diff --git a/src/sat/sat_bdd.h b/src/sat/sat_bdd.h index 41d1115b9..70f6960fe 100644 --- a/src/sat/sat_bdd.h +++ b/src/sat/sat_bdd.h @@ -32,6 +32,8 @@ namespace sat { typedef unsigned BDD; + const BDD null_bdd = UINT_MAX; + enum bdd_op { bdd_and_op = 2, bdd_or_op = 3, diff --git a/src/sat/sat_simplifier.cpp b/src/sat/sat_simplifier.cpp index 4fd9b08be..d19cd14d4 100644 --- a/src/sat/sat_simplifier.cpp +++ b/src/sat/sat_simplifier.cpp @@ -338,8 +338,7 @@ namespace sat { if (sz == 0) { s.set_conflict(justification()); for (; it != end; ++it, ++it2) { - *it2 = *it; - ++it2; + *it2 = *it; } break; } diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp index e1e0e0b0a..8b7e2e63c 100644 --- a/src/sat/sat_solver/inc_sat_solver.cpp +++ b/src/sat/sat_solver/inc_sat_solver.cpp @@ -299,7 +299,7 @@ public: if (m_preprocess) m_preprocess->collect_statistics(st); m_solver.collect_statistics(st); } - void get_unsat_core(ptr_vector & r) override { + void get_unsat_core(expr_ref_vector & r) override { r.reset(); r.append(m_core.size(), m_core.c_ptr()); } diff --git a/src/sat/tactic/atom2bool_var.cpp b/src/sat/tactic/atom2bool_var.cpp index e3c9b6767..b79eaa251 100644 --- a/src/sat/tactic/atom2bool_var.cpp +++ b/src/sat/tactic/atom2bool_var.cpp @@ -75,7 +75,7 @@ struct collect_boolean_interface_proc { continue; if (is_app(t) && to_app(t)->get_family_id() == m.get_basic_family_id() && to_app(t)->get_num_args() > 0) { decl_kind k = to_app(t)->get_decl_kind(); - if (k == OP_OR || k == OP_NOT || k == OP_IFF || ((k == OP_EQ || k == OP_ITE) && m.is_bool(to_app(t)->get_arg(1)))) { + if (k == OP_OR || k == OP_NOT || ((k == OP_EQ || k == OP_ITE) && m.is_bool(to_app(t)->get_arg(1)))) { unsigned num = to_app(t)->get_num_args(); for (unsigned i = 0; i < num; i++) { expr * arg = to_app(t)->get_arg(i); diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index ea9c5b4ea..5f0fbec16 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -201,7 +201,6 @@ struct goal2sat::imp { case OP_NOT: case OP_OR: case OP_AND: - case OP_IFF: m_frame_stack.push_back(frame(to_app(t), root, sign, 0)); return false; case OP_ITE: @@ -630,7 +629,6 @@ struct goal2sat::imp { case OP_ITE: convert_ite(t, root, sign); break; - case OP_IFF: case OP_EQ: convert_iff(t, root, sign); break; @@ -970,9 +968,9 @@ model_converter* sat2goal::mc::translate(ast_translation& translator) { return result; } -void sat2goal::mc::collect(ast_pp_util& visitor) { +void sat2goal::mc::set_env(ast_pp_util* visitor) { flush_gmc(); - if (m_gmc) m_gmc->collect(visitor); + if (m_gmc) m_gmc->set_env(visitor); } void sat2goal::mc::display(std::ostream& out) { diff --git a/src/sat/tactic/goal2sat.h b/src/sat/tactic/goal2sat.h index 32b89fe5d..d86b2d7e4 100644 --- a/src/sat/tactic/goal2sat.h +++ b/src/sat/tactic/goal2sat.h @@ -92,7 +92,7 @@ public: void operator()(model_ref& md) override; void operator()(expr_ref& fml) override; model_converter* translate(ast_translation& translator) override; - void collect(ast_pp_util& visitor) override; + void set_env(ast_pp_util* visitor) override; void display(std::ostream& out) override; void get_units(obj_map& units) override; app* var2expr(sat::bool_var v) const { return m_var2expr.get(v, nullptr); } diff --git a/src/shell/opt_frontend.cpp b/src/shell/opt_frontend.cpp index 5af5bfdd7..0a2a8f4a1 100644 --- a/src/shell/opt_frontend.cpp +++ b/src/shell/opt_frontend.cpp @@ -121,10 +121,8 @@ static unsigned parse_opt(std::istream& in, opt_format f) { expr_ref_vector hard(m); opt.get_hard_constraints(hard); for (expr* h : hard) { - expr_ref tmp(m); - VERIFY(mdl->eval(h, tmp)); - if (!m.is_true(tmp)) { - std::cout << mk_pp(h, m) << " " << tmp << "\n"; + if (!mdl->is_true(h)) { + std::cout << mk_pp(h, m) << " evaluates to: " << (*mdl)(h) << "\n"; } } } diff --git a/src/smt/asserted_formulas.cpp b/src/smt/asserted_formulas.cpp index c00fb93b1..d364404da 100644 --- a/src/smt/asserted_formulas.cpp +++ b/src/smt/asserted_formulas.cpp @@ -501,7 +501,7 @@ unsigned asserted_formulas::propagate_values(unsigned i) { void asserted_formulas::update_substitution(expr* n, proof* pr) { expr* lhs, *rhs, *n1; proof_ref pr1(m); - if (is_ground(n) && (m.is_eq(n, lhs, rhs) || m.is_iff(n, lhs, rhs))) { + if (is_ground(n) && m.is_eq(n, lhs, rhs)) { compute_depth(lhs); compute_depth(rhs); if (is_gt(lhs, rhs)) { @@ -517,7 +517,7 @@ void asserted_formulas::update_substitution(expr* n, proof* pr) { } TRACE("propagate_values", tout << "incompatible " << mk_pp(n, m) << "\n";); } - if (m.is_not(n, n1)) { + if (m.is_not(n, n1)) { pr1 = m.proofs_enabled() ? m.mk_iff_false(pr) : nullptr; m_scoped_substitution.insert(n1, m.mk_false(), pr1); } @@ -639,4 +639,3 @@ void pp(asserted_formulas & f) { f.display(std::cout); } #endif - diff --git a/src/smt/cost_evaluator.cpp b/src/smt/cost_evaluator.cpp index 94151f2b3..0719d0961 100644 --- a/src/smt/cost_evaluator.cpp +++ b/src/smt/cost_evaluator.cpp @@ -47,8 +47,7 @@ float cost_evaluator::eval(expr * f) const { return 1.0f; return 0.0f; case OP_ITE: return E(0) != 0.0f ? E(1) : E(2); - case OP_EQ: - case OP_IFF: return E(0) == E(1) ? 1.0f : 0.0f; + case OP_EQ: return E(0) == E(1) ? 1.0f : 0.0f; case OP_XOR: return E(0) != E(1) ? 1.0f : 0.0f; case OP_IMPLIES: if (E(0) == 0.0f) diff --git a/src/smt/expr_context_simplifier.cpp b/src/smt/expr_context_simplifier.cpp index bce28420d..a57b0299f 100644 --- a/src/smt/expr_context_simplifier.cpp +++ b/src/smt/expr_context_simplifier.cpp @@ -110,13 +110,15 @@ void expr_context_simplifier::reduce_rec(app * a, expr_ref & result) { case OP_OR: reduce_or(a->get_num_args(), a->get_args(), result); return; - case OP_IFF: { - expr_ref tmp1(m_manager), tmp2(m_manager); - reduce_rec(a->get_arg(0), tmp1); - reduce_rec(a->get_arg(1), tmp2); - m_simp.mk_iff(tmp1.get(), tmp2.get(), result); - return; - } + case OP_EQ: + if (m_manager.is_iff(a)) { + expr_ref tmp1(m_manager), tmp2(m_manager); + reduce_rec(a->get_arg(0), tmp1); + reduce_rec(a->get_arg(1), tmp2); + m_simp.mk_iff(tmp1.get(), tmp2.get(), result); + return; + } + break; case OP_XOR: { expr_ref tmp1(m_manager), tmp2(m_manager); reduce_rec(a->get_arg(0), tmp1); @@ -580,7 +582,7 @@ void expr_strong_context_simplifier::simplify_model_based(expr* fml, expr_ref& r } assignment_map.insert(a, value); } - else if (m.is_iff(a, n1, n2) || m.is_eq(a, n1, n2)) { + else if (m.is_eq(a, n1, n2)) { lbool v1 = assignment_map.find(n1); lbool v2 = assignment_map.find(n2); if (v1 == l_undef || v2 == l_undef) { diff --git a/src/smt/mam.cpp b/src/smt/mam.cpp index caf677a85..51c39f9e2 100644 --- a/src/smt/mam.cpp +++ b/src/smt/mam.cpp @@ -569,10 +569,9 @@ namespace smt { if (m_context) { ast_manager & m = m_context->get_manager(); out << "patterns:\n"; - ptr_vector::const_iterator it = m_patterns.begin(); - ptr_vector::const_iterator end = m_patterns.end(); - for (; it != end; ++it) - out << mk_pp(*it, m) << "\n"; + for (expr* p : m_patterns) { + out << mk_pp(p, m) << "\n"; + } } #endif out << "function: " << m_root_lbl->get_name(); @@ -831,10 +830,8 @@ namespace smt { void init(code_tree * t, quantifier * qa, app * mp, unsigned first_idx) { SASSERT(m_ast_manager.is_pattern(mp)); #ifdef Z3DEBUG - svector::iterator it = m_mark.begin(); - svector::iterator end = m_mark.end(); - for (; it != end; ++it) { - SASSERT(*it == NOT_CHECKED); + for (auto cm : m_mark) { + SASSERT(cm == NOT_CHECKED); } #endif m_tree = t; @@ -865,9 +862,7 @@ namespace smt { That is, during execution time, the variables will be already bound */ bool all_args_are_bound_vars(app * n) { - unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - expr * arg = n->get_arg(i); + for (expr* arg : *n) { if (!is_var(arg)) return false; if (m_vars[to_var(arg)->get_idx()] == -1) @@ -884,9 +879,7 @@ namespace smt { if (n->is_ground()) { return; } - unsigned num_args = n->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - expr * arg = n->get_arg(i); + for (expr* arg : *n) { if (is_var(arg)) { sz++; unsigned var_id = to_var(arg)->get_idx(); @@ -928,10 +921,7 @@ namespace smt { unsigned first_app_sz; unsigned first_app_num_unbound_vars; // generate first the non-BIND operations - unsigned_vector::iterator it = m_todo.begin(); - unsigned_vector::iterator end = m_todo.end(); - for (; it != end; ++it) { - unsigned reg = *it; + for (unsigned reg : m_todo) { expr * p = m_registers[reg]; SASSERT(!is_quantifier(p)); if (is_var(p)) { @@ -1249,10 +1239,7 @@ namespace smt { SASSERT(head->m_next == 0); m_seq.push_back(m_ct_manager.mk_yield(m_qa, m_mp, m_qa->get_num_decls(), reinterpret_cast(m_vars.begin()))); - ptr_vector::iterator it = m_seq.begin(); - ptr_vector::iterator end = m_seq.end(); - for (; it != end; ++it) { - instruction * curr = *it; + for (instruction * curr : m_seq) { head->m_next = curr; head = curr; } @@ -1495,10 +1482,8 @@ namespace smt { } if (num_instr > SIMPLE_SEQ_THRESHOLD || (curr != nullptr && curr->m_opcode == CHOOSE)) simple = false; - unsigned_vector::iterator it = m_to_reset.begin(); - unsigned_vector::iterator end = m_to_reset.end(); - for (; it != end; ++it) - m_registers[*it] = 0; + for (unsigned reg : m_to_reset) + m_registers[reg] = 0; return weight; } @@ -1716,23 +1701,19 @@ namespace smt { m_num_choices++; // set: head -> c1 -> c2 -> c3 -> new_child_head1 curr = head; - ptr_vector::iterator it1 = m_compatible.begin(); - ptr_vector::iterator end1 = m_compatible.end(); - for (; it1 != end1; ++it1) { - set_next(curr, *it1); - curr = *it1; + for (instruction* instr : m_compatible) { + set_next(curr, instr); + curr = instr; } set_next(curr, new_child_head1); // set: new_child_head1:CHOOSE(new_child_head2) -> i1 -> i2 -> first_child_head curr = new_child_head1; - ptr_vector::iterator it2 = m_incompatible.begin(); - ptr_vector::iterator end2 = m_incompatible.end(); - for (; it2 != end2; ++it2) { + for (instruction* inc : m_incompatible) { if (curr == new_child_head1) - curr->m_next = *it2; // new_child_head1 is a new node, I don't need to save trail + curr->m_next = inc; // new_child_head1 is a new node, I don't need to save trail else - set_next(curr, *it2); - curr = *it2; + set_next(curr, inc); + curr = inc; } set_next(curr, first_child_head); // build new_child_head2:NOOP -> linearise() @@ -3370,10 +3351,7 @@ namespace smt { void update_vars(unsigned short var_id, path * p, quantifier * qa, app * mp) { paths & var_paths = m_var_paths[var_id]; bool found = false; - paths::iterator it = var_paths.begin(); - paths::iterator end = var_paths.end(); - for (; it != end; ++it) { - path * curr_path = *it; + for (path* curr_path : var_paths) { if (is_equal(p, curr_path)) found = true; func_decl * lbl1 = curr_path->m_label; @@ -3664,18 +3642,12 @@ namespace smt { TRACE("incremental_matcher", tout << "pp: plbls1: " << plbls1 << ", plbls2: " << plbls2 << "\n";); TRACE("mam_info", tout << "pp: " << plbls1.size() * plbls2.size() << "\n";); if (!plbls1.empty() && !plbls2.empty()) { - approx_set::iterator it1 = plbls1.begin(); - approx_set::iterator end1 = plbls1.end(); - for (; it1 != end1; ++it1) { + for (unsigned plbl1 : plbls1) { if (m_context.get_cancel_flag()) { break; } - unsigned plbl1 = *it1; SASSERT(plbls1.may_contain(plbl1)); - approx_set::iterator it2 = plbls2.begin(); - approx_set::iterator end2 = plbls2.end(); - for (; it2 != end2; ++it2) { - unsigned plbl2 = *it2; + for (unsigned plbl2 : plbls2) { SASSERT(plbls2.may_contain(plbl2)); unsigned n_plbl1 = plbl1; unsigned n_plbl2 = plbl2; diff --git a/src/smt/params/preprocessor_params.cpp b/src/smt/params/preprocessor_params.cpp index ee4b7c2e4..3e1c6f0cd 100644 --- a/src/smt/params/preprocessor_params.cpp +++ b/src/smt/params/preprocessor_params.cpp @@ -46,7 +46,6 @@ void preprocessor_params::display(std::ostream & out) const { DISPLAY_PARAM(m_eliminate_term_ite); DISPLAY_PARAM(m_macro_finder); DISPLAY_PARAM(m_propagate_values); - DISPLAY_PARAM(m_propagate_booleans); DISPLAY_PARAM(m_refine_inj_axiom); DISPLAY_PARAM(m_eliminate_bounds); DISPLAY_PARAM(m_simplify_bit2int); diff --git a/src/smt/params/preprocessor_params.h b/src/smt/params/preprocessor_params.h index be7fd4c01..f6724fada 100644 --- a/src/smt/params/preprocessor_params.h +++ b/src/smt/params/preprocessor_params.h @@ -37,7 +37,6 @@ struct preprocessor_params : public pattern_inference_params, bool m_eliminate_term_ite; bool m_macro_finder; bool m_propagate_values; - bool m_propagate_booleans; bool m_refine_inj_axiom; bool m_eliminate_bounds; bool m_simplify_bit2int; @@ -59,7 +58,6 @@ public: m_eliminate_term_ite(false), m_macro_finder(false), m_propagate_values(true), - m_propagate_booleans(false), // TODO << check peformance m_refine_inj_axiom(true), m_eliminate_bounds(false), m_simplify_bit2int(false), diff --git a/src/smt/params/qi_params.cpp b/src/smt/params/qi_params.cpp index a9cff6e8c..91f354eda 100644 --- a/src/smt/params/qi_params.cpp +++ b/src/smt/params/qi_params.cpp @@ -35,12 +35,12 @@ void qi_params::updt_params(params_ref const & _p) { m_qi_lazy_threshold = p.qi_lazy_threshold(); m_qi_cost = p.qi_cost(); m_qi_max_eager_multipatterns = p.qi_max_multi_patterns(); + m_qi_quick_checker = static_cast(p.qi_quick_checker()); } #define DISPLAY_PARAM(X) out << #X"=" << X << std::endl; void qi_params::display(std::ostream & out) const { - DISPLAY_PARAM(m_qi_ematching); DISPLAY_PARAM(m_qi_cost); DISPLAY_PARAM(m_qi_new_gen); DISPLAY_PARAM(m_qi_eager_threshold); diff --git a/src/smt/params/qi_params.h b/src/smt/params/qi_params.h index cc1a30673..0f6c03f5b 100644 --- a/src/smt/params/qi_params.h +++ b/src/smt/params/qi_params.h @@ -29,7 +29,6 @@ enum quick_checker_mode { }; struct qi_params { - bool m_qi_ematching; std::string m_qi_cost; std::string m_qi_new_gen; double m_qi_eager_threshold; diff --git a/src/smt/params/smt_params_helper.pyg b/src/smt/params/smt_params_helper.pyg index a85365de0..816764896 100644 --- a/src/smt/params/smt_params_helper.pyg +++ b/src/smt/params/smt_params_helper.pyg @@ -36,6 +36,7 @@ def_module_params(module_name='smt', ('qi.lazy_threshold', DOUBLE, 20.0, 'threshold for lazy quantifier instantiation'), ('qi.cost', STRING, '(+ weight generation)', 'expression specifying what is the cost of a given quantifier instantiation'), ('qi.max_multi_patterns', UINT, 0, 'specify the number of extra multi patterns'), + ('qi.quick_checker', UINT, 0, 'specify quick checker mode, 0 - no quick checker, 1 - using unsat instances, 2 - using both unsat and no-sat instances'), ('bv.reflect', BOOL, True, 'create enode for every bit-vector term'), ('bv.enable_int2bv', BOOL, True, 'enable support for int2bv and bv2int operators'), ('arith.random_initial_value', BOOL, False, 'use random initial values in the simplex-based procedure for linear arithmetic'), @@ -53,6 +54,8 @@ def_module_params(module_name='smt', ('arith.ignore_int', BOOL, False, 'treat integer variables as real'), ('arith.dump_lemmas', BOOL, False, 'dump arithmetic theory lemmas to files'), ('arith.greatest_error_pivot', BOOL, False, 'Pivoting strategy'), + ('arith.eager_eq_axioms', BOOL, True, 'eager equality axioms'), + ('arith.auto_config_simplex', BOOL, False, 'force simplex solver in auto_config'), ('pb.conflict_frequency', UINT, 1000, 'conflict frequency for Pseudo-Boolean theory'), ('pb.learn_complements', BOOL, True, 'learn complement literals for Pseudo-Boolean theory'), ('pb.enable_compilation', BOOL, True, 'enable compilation into sorting circuits for Pseudo-Boolean'), diff --git a/src/smt/params/theory_arith_params.cpp b/src/smt/params/theory_arith_params.cpp index 250848db4..0918c9423 100644 --- a/src/smt/params/theory_arith_params.cpp +++ b/src/smt/params/theory_arith_params.cpp @@ -37,6 +37,9 @@ void theory_arith_params::updt_params(params_ref const & _p) { m_arith_bound_prop = static_cast(p.arith_propagation_mode()); m_arith_dump_lemmas = p.arith_dump_lemmas(); m_arith_reflect = p.arith_reflect(); + m_arith_eager_eq_axioms = p.arith_eager_eq_axioms(); + m_arith_auto_config_simplex = p.arith_auto_config_simplex(); + arith_rewriter_params ap(_p); m_arith_eq2ineq = ap.eq2ineq(); } diff --git a/src/smt/proto_model/proto_model.cpp b/src/smt/proto_model/proto_model.cpp index 688ded834..8af3af277 100644 --- a/src/smt/proto_model/proto_model.cpp +++ b/src/smt/proto_model/proto_model.cpp @@ -342,10 +342,16 @@ void proto_model::compress() { \brief Complete the interpretation fi of f if it is partial. If f does not have an interpretation in the given model, then this is a noop. */ -void proto_model::complete_partial_func(func_decl * f) { +void proto_model::complete_partial_func(func_decl * f, bool use_fresh) { func_interp * fi = get_func_interp(f); if (fi && fi->is_partial()) { - expr * else_value = fi->get_max_occ_result(); + expr * else_value; + if (use_fresh) { + else_value = get_fresh_value(f->get_range()); + } + else { + else_value = fi->get_max_occ_result(); + } if (else_value == nullptr) else_value = get_some_value(f->get_range()); fi->set_else(else_value); @@ -355,14 +361,14 @@ void proto_model::complete_partial_func(func_decl * f) { /** \brief Set the (else) field of function interpretations... */ -void proto_model::complete_partial_funcs() { +void proto_model::complete_partial_funcs(bool use_fresh) { if (m_model_partial) return; // m_func_decls may be "expanded" when we invoke get_some_value. // So, we must not use iterators to traverse it. - for (unsigned i = 0; i < m_func_decls.size(); i++) { - complete_partial_func(m_func_decls[i]); + for (unsigned i = 0; i < m_func_decls.size(); ++i) { + complete_partial_func(m_func_decls.get(i), use_fresh); } } diff --git a/src/smt/proto_model/proto_model.h b/src/smt/proto_model/proto_model.h index d92d459e4..04e3a90fe 100644 --- a/src/smt/proto_model/proto_model.h +++ b/src/smt/proto_model/proto_model.h @@ -100,8 +100,8 @@ public: // // Complete partial function interps // - void complete_partial_func(func_decl * f); - void complete_partial_funcs(); + void complete_partial_func(func_decl * f, bool use_fresh); + void complete_partial_funcs(bool use_fresh); // // Create final model object. diff --git a/src/smt/smt_checker.cpp b/src/smt/smt_checker.cpp index ed80eaab7..1b6a5d370 100644 --- a/src/smt/smt_checker.cpp +++ b/src/smt/smt_checker.cpp @@ -61,8 +61,20 @@ namespace smt { return is_true ? any_arg(a, true) : all_args(a, false); case OP_AND: return is_true ? all_args(a, true) : any_arg(a, false); - case OP_IFF: - if (is_true) { + case OP_EQ: + if (!m_manager.is_iff(a)) { + enode * lhs = get_enode_eq_to(a->get_arg(0)); + enode * rhs = get_enode_eq_to(a->get_arg(1)); + if (lhs && rhs && m_context.is_relevant(lhs) && m_context.is_relevant(rhs)) { + if (is_true && lhs->get_root() == rhs->get_root()) + return true; + // if (!is_true && m_context.is_ext_diseq(lhs, rhs, 2)) + if (!is_true && m_context.is_diseq(lhs, rhs)) + return true; + } + return false; + } + else if (is_true) { return (check(a->get_arg(0), true) && check(a->get_arg(1), true)) || @@ -86,18 +98,6 @@ namespace smt { } return check(a->get_arg(1), is_true) && check(a->get_arg(2), is_true); } - case OP_EQ: { - enode * lhs = get_enode_eq_to(a->get_arg(0)); - enode * rhs = get_enode_eq_to(a->get_arg(1)); - if (lhs && rhs && m_context.is_relevant(lhs) && m_context.is_relevant(rhs)) { - if (is_true && lhs->get_root() == rhs->get_root()) - return true; - // if (!is_true && m_context.is_ext_diseq(lhs, rhs, 2)) - if (!is_true && m_context.is_diseq(lhs, rhs)) - return true; - } - return false; - } default: break; } diff --git a/src/smt/smt_clause.cpp b/src/smt/smt_clause.cpp index a9365fffc..2b9b8dd3e 100644 --- a/src/smt/smt_clause.cpp +++ b/src/smt/smt_clause.cpp @@ -25,7 +25,7 @@ namespace smt { \brief Create a new clause. bool_var2expr_map is a mapping from bool_var -> expr, it is only used if save_atoms == true. */ - clause * clause::mk(ast_manager & m, unsigned num_lits, literal * lits, clause_kind k, justification * js, + clause * clause::mk(ast_manager & m, unsigned num_lits, literal * lits, clause_kind k, justification * js, clause_del_eh * del_eh, bool save_atoms, expr * const * bool_var2expr_map) { SASSERT(k == CLS_AUX || js == 0 || !js->in_region()); SASSERT(num_lits >= 2); @@ -67,7 +67,7 @@ namespace smt { }}); return cls; } - + void clause::deallocate(ast_manager & m) { clause_del_eh * del_eh = get_del_eh(); if (del_eh) @@ -115,4 +115,3 @@ namespace smt { } }; - diff --git a/src/smt/smt_clause.h b/src/smt/smt_clause.h index 8e843c4cf..f0b352e05 100644 --- a/src/smt/smt_clause.h +++ b/src/smt/smt_clause.h @@ -192,13 +192,13 @@ namespace smt { return m_lits[idx]; } - literal * begin_literals() { return m_lits; } + literal * begin() { return m_lits; } - literal * end_literals() { return m_lits + m_num_literals; } + literal * end() { return m_lits + m_num_literals; } - literal const * begin_literals() const { return m_lits; } + literal const * begin() const { return m_lits; } - literal const * end_literals() const { return m_lits + m_num_literals; } + literal const * end() const { return m_lits + m_num_literals; } unsigned get_activity() const { SASSERT(is_lemma()); diff --git a/src/smt/smt_conflict_resolution.cpp b/src/smt/smt_conflict_resolution.cpp index 379846ed7..f7bc60051 100644 --- a/src/smt/smt_conflict_resolution.cpp +++ b/src/smt/smt_conflict_resolution.cpp @@ -771,7 +771,7 @@ namespace smt { app * fact = to_app(m_manager.get_fact(pr)); app * n1_owner = n1->get_owner(); app * n2_owner = n2->get_owner(); - bool is_eq = m_manager.is_eq(fact) || m_manager.is_iff(fact); + bool is_eq = m_manager.is_eq(fact); if (!is_eq || (fact->get_arg(0) != n2_owner && fact->get_arg(1) != n2_owner)) { CTRACE("norm_eq_proof_bug", !m_ctx.is_true(n2) && !m_ctx.is_false(n2), tout << "n1: #" << n1->get_owner_id() << ", n2: #" << n2->get_owner_id() << "\n"; @@ -794,7 +794,7 @@ namespace smt { TRACE("norm_eq_proof", tout << "#" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n"; tout << mk_ll_pp(pr, m_manager, true, false);); - SASSERT(m_manager.is_eq(fact) || m_manager.is_iff(fact)); + SASSERT(m_manager.is_eq(fact)); SASSERT((fact->get_arg(0) == n1->get_owner() && fact->get_arg(1) == n2->get_owner()) || (fact->get_arg(1) == n1->get_owner() && fact->get_arg(0) == n2->get_owner())); if (fact->get_arg(0) == n1_owner && fact->get_arg(1) == n2_owner) @@ -1033,6 +1033,7 @@ namespace smt { return pr; } SASSERT(js != 0); + TRACE("proof_gen_bug", tout << js << "\n";); m_todo_pr.push_back(tp_elem(js)); return nullptr; } diff --git a/src/smt/smt_conflict_resolution.h b/src/smt/smt_conflict_resolution.h index b5b857184..90390ae7e 100644 --- a/src/smt/smt_conflict_resolution.h +++ b/src/smt/smt_conflict_resolution.h @@ -96,7 +96,7 @@ namespace smt { }; tp_elem(literal l):m_kind(LITERAL), m_lidx(l.index()) {} tp_elem(enode * lhs, enode * rhs):m_kind(EQUALITY), m_lhs(lhs), m_rhs(rhs) {} - tp_elem(justification * js):m_kind(JUSTIFICATION), m_js(js) {} + tp_elem(justification * js):m_kind(JUSTIFICATION), m_js(js) { SASSERT(js);} }; svector m_todo_pr; diff --git a/src/smt/smt_consequences.cpp b/src/smt/smt_consequences.cpp index 13fd9e6ea..4cb331661 100644 --- a/src/smt/smt_consequences.cpp +++ b/src/smt/smt_consequences.cpp @@ -637,15 +637,14 @@ namespace smt { model_ref mdl; for (unsigned i = 0; i < unfixed.size(); ++i) { push(); - for (unsigned j = 0; j < assumptions.size(); ++j) { - assert_expr(assumptions[j]); - } + for (expr* a : assumptions) + assert_expr(a); TRACE("context", tout << "checking unfixed: " << mk_pp(unfixed[i], m) << "\n";); lbool is_sat = check(); SASSERT(is_sat != l_false); if (is_sat == l_true) { get_model(mdl); - mdl->eval(unfixed[i], tmp); + tmp = (*mdl)(unfixed[i]); if (m.is_value(tmp)) { tmp = m.mk_not(m.mk_eq(unfixed[i], tmp)); assert_expr(tmp); diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index 86147e259..bf1f789c4 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -203,8 +203,8 @@ namespace smt { literal l1 = to_literal(l_idx); literal neg_l1 = ~l1; watch_list const & wl = *it; - literal const * it2 = wl.begin_literals(); - literal const * end2 = wl.end_literals(); + literal const * it2 = wl.begin(); + literal const * end2 = wl.end(); for (; it2 != end2; ++it2) { literal l2 = *it2; if (l1.index() < l2.index()) { @@ -385,8 +385,8 @@ namespace smt { it2++; } else { - literal * it3 = cls->begin_literals() + 2; - literal * end3 = cls->end_literals(); + literal * it3 = cls->begin() + 2; + literal * end3 = cls->end(); for(; it3 != end3; ++it3) { if (get_assignment(*it3) != l_false) { // swap literal *it3 with literal at position 0 @@ -628,7 +628,7 @@ namespace smt { */ void context::remove_parents_from_cg_table(enode * r1) { // Remove parents from the congruence table - for (enode * parent : r1->get_parents()) { + for (enode * parent : enode::parents(r1)) { #if 0 { static unsigned num_eqs = 0; @@ -673,7 +673,7 @@ namespace smt { */ void context::reinsert_parents_into_cg_table(enode * r1, enode * r2, enode * n1, enode * n2, eq_justification js) { enode_vector & r2_parents = r2->m_parents; - for (enode * parent : r1->get_parents()) { + for (enode * parent : enode::parents(r1)) { if (!parent->is_marked()) continue; parent->unset_mark(); @@ -1005,7 +1005,7 @@ namespace smt { r2->m_parents.shrink(r2_num_parents); // try to reinsert parents of r1 that are not cgr - for (enode * parent : r1->get_parents()) { + for (enode * parent : enode::parents(r1)) { TRACE("add_eq_parents", tout << "visiting: #" << parent->get_owner_id() << "\n";); if (parent->is_cgc_enabled()) { enode * cg = parent->m_cg; @@ -1201,7 +1201,7 @@ namespace smt { bool context::is_diseq_slow(enode * n1, enode * n2) const { if (n1->get_num_parents() > n2->get_num_parents()) std::swap(n1, n2); - for (enode * parent : n1->get_parents()) { + for (enode * parent : enode::parents(n1)) { if (parent->is_eq() && is_relevant(parent->get_owner()) && get_assignment(enode2bool_var(parent)) == l_false && ((parent->get_arg(0)->get_root() == n1->get_root() && parent->get_arg(1)->get_root() == n2->get_root()) || (parent->get_arg(1)->get_root() == n1->get_root() && parent->get_arg(0)->get_root() == n2->get_root()))) { @@ -1233,7 +1233,7 @@ namespace smt { return false; if (r1->get_num_parents() < SMALL_NUM_PARENTS) { TRACE("is_ext_diseq", tout << mk_bounded_pp(n1->get_owner(), m_manager) << " " << mk_bounded_pp(n2->get_owner(), m_manager) << " " << depth << "\n";); - for (enode* p1 : r1->get_parents()) { + for (enode * p1 : enode::parents(r1)) { if (!is_relevant(p1)) continue; if (p1->is_eq()) @@ -1243,7 +1243,7 @@ namespace smt { func_decl * f = p1->get_decl(); TRACE("is_ext_diseq", tout << "p1: " << mk_bounded_pp(p1->get_owner(), m_manager) << "\n";); unsigned num_args = p1->get_num_args(); - for (enode * p2 : r2->get_parents()) { + for (enode * p2 : enode::parents(r2)) { if (!is_relevant(p2)) continue; if (p2->is_eq()) @@ -1281,7 +1281,7 @@ namespace smt { } almost_cg_table & table = *(m_almost_cg_tables[depth]); table.reset(r1, r2); - for (enode* p1 : r1->get_parents()) { + for (enode * p1 : enode::parents(r1)) { if (!is_relevant(p1)) continue; if (p1->is_eq()) @@ -1292,7 +1292,7 @@ namespace smt { } if (table.empty()) return false; - for (enode * p2 : r2->get_parents()) { + for (enode * p2 : enode::parents(r2)) { if (!is_relevant(p2)) continue; if (p2->is_eq()) @@ -1817,6 +1817,17 @@ namespace smt { more case splits to be performed. */ bool context::decide() { + + if (at_search_level() && !m_tmp_clauses.empty()) { + switch (decide_clause()) { + case l_true: // already satisfied + break; + case l_undef: // made a decision + return true; + case l_false: // inconsistent + return false; + } + } bool_var var; lbool phase; m_case_split_queue->next_case_split(var, phase); @@ -2156,7 +2167,7 @@ namespace smt { \brief See cache_generation(unsigned new_scope_lvl) */ void context::cache_generation(clause const * cls, unsigned new_scope_lvl) { - cache_generation(cls->get_num_literals(), cls->begin_literals(), new_scope_lvl); + cache_generation(cls->get_num_literals(), cls->begin(), new_scope_lvl); } /** @@ -2640,10 +2651,11 @@ namespace smt { } TRACE("simplify_clauses_detail", tout << "before:\n"; display_clauses(tout, m_lemmas);); + IF_VERBOSE(2, verbose_stream() << "(smt.simplifying-clause-set"; verbose_stream().flush();); + SASSERT(check_clauses(m_lemmas)); SASSERT(check_clauses(m_aux_clauses)); - IF_VERBOSE(2, verbose_stream() << "(smt.simplifying-clause-set"; verbose_stream().flush();); // m_simp_counter is used to balance the cost of simplify_clause. // @@ -2911,6 +2923,7 @@ namespace smt { del_clauses(m_aux_clauses, 0); del_clauses(m_lemmas, 0); del_justifications(m_justifications, 0); + reset_tmp_clauses(); if (m_is_diseq_tmp) { m_is_diseq_tmp->del_eh(m_manager, false); m_manager.dec_ref(m_is_diseq_tmp->get_owner()); @@ -3053,12 +3066,47 @@ namespace smt { bool context::reduce_assertions() { if (!m_asserted_formulas.inconsistent()) { - SASSERT(at_base_level()); + // SASSERT(at_base_level()); m_asserted_formulas.reduce(); } return m_asserted_formulas.inconsistent(); } + static bool is_valid_assumption(ast_manager & m, expr * assumption) { + expr* arg; + if (!m.is_bool(assumption)) + return false; + if (is_uninterp_const(assumption)) + return true; + if (m.is_not(assumption, arg) && is_uninterp_const(arg)) + return true; + if (!is_app(assumption)) + return false; + if (to_app(assumption)->get_num_args() == 0) + return true; + if (m.is_not(assumption, arg) && is_app(arg) && to_app(arg)->get_num_args() == 0) + return true; + return false; + } + + void context::internalize_proxies(expr_ref_vector const& asms, vector>& asm2proxy) { + for (expr* e : asms) { + if (is_valid_assumption(m_manager, e)) { + asm2proxy.push_back(std::make_pair(e, expr_ref(e, m_manager))); + } + else { + expr_ref proxy(m_manager), fml(m_manager); + proxy = m_manager.mk_fresh_const("proxy", m_manager.mk_bool_sort()); + fml = m_manager.mk_implies(proxy, e); + m_asserted_formulas.assert_expr(fml); + asm2proxy.push_back(std::make_pair(e, proxy)); + } + } + // The new assertions are of the form 'proxy => assumption' + // so clause simplification is sound even as these are removed after pop_scope. + internalize_assertions(); + } + void context::internalize_assertions() { if (get_cancel_flag()) return; TRACE("internalize_assertions", tout << "internalize_assertions()...\n";); @@ -3091,32 +3139,16 @@ namespace smt { } TRACE("internalize_assertions", tout << "after internalize_assertions()...\n"; tout << "inconsistent: " << inconsistent() << "\n";); - } - - bool is_valid_assumption(ast_manager & m, expr * assumption) { - expr* arg; - if (!m.is_bool(assumption)) - return false; - if (is_uninterp_const(assumption)) - return true; - if (m.is_not(assumption, arg) && is_uninterp_const(arg)) - return true; - if (!is_app(assumption)) - return false; - if (to_app(assumption)->get_num_args() == 0) - return true; - if (m.is_not(assumption, arg) && is_app(arg) && to_app(arg)->get_num_args() == 0) - return true; - return false; + TRACE("after_internalize_assertions", display(tout);); } /** \brief Assumptions must be uninterpreted boolean constants (aka propositional variables). */ - bool context::validate_assumptions(unsigned num_assumptions, expr * const * assumptions) { - for (unsigned i = 0; i < num_assumptions; i++) { - SASSERT(assumptions[i]); - if (!is_valid_assumption(m_manager, assumptions[i])) { + bool context::validate_assumptions(expr_ref_vector const& asms) { + for (expr* a : asms) { + SASSERT(a); + if (!is_valid_assumption(m_manager, a)) { warning_msg("an assumption must be a propositional variable or the negation of one"); return false; } @@ -3124,11 +3156,69 @@ namespace smt { return true; } - void context::init_assumptions(unsigned num_assumptions, expr * const * assumptions) { + void context::init_clause(expr_ref_vector const& _clause) { + literal_vector lits; + for (expr* lit : _clause) { + internalize_formula(lit, true); + mark_as_relevant(lit); + lits.push_back(get_literal(lit)); + } + clause* clausep = nullptr; + if (lits.size() >= 2) { + justification* js = nullptr; + if (m_manager.proofs_enabled()) { + proof * pr = mk_clause_def_axiom(lits.size(), lits.c_ptr(), nullptr); + js = mk_justification(justification_proof_wrapper(*this, pr)); + } + clausep = clause::mk(m_manager, lits.size(), lits.c_ptr(), CLS_AUX, js); + } + m_tmp_clauses.push_back(std::make_pair(clausep, lits)); + } + + void context::reset_tmp_clauses() { + for (auto& p : m_tmp_clauses) { + if (p.first) del_clause(p.first); + } + m_tmp_clauses.reset(); + } + + lbool context::decide_clause() { + if (m_tmp_clauses.empty()) return l_true; + for (auto & tmp_clause : m_tmp_clauses) { + literal_vector& lits = tmp_clause.second; + for (literal l : lits) { + switch (get_assignment(l)) { + case l_false: + break; + case l_true: + goto next_clause; + default: + shuffle(lits.size(), lits.c_ptr(), m_random); + push_scope(); + assign(l, b_justification::mk_axiom(), true); + return l_undef; + } + } + + if (lits.size() == 1) { + set_conflict(b_justification(), ~lits[0]); + } + else { + set_conflict(b_justification(tmp_clause.first), null_literal); + } + VERIFY(!resolve_conflict()); + return l_false; + next_clause: + ; + } + return l_true; + } + + void context::init_assumptions(expr_ref_vector const& asms) { reset_assumptions(); m_literal2assumption.reset(); m_unsat_core.reset(); - if (num_assumptions > 0) { + if (!asms.empty()) { // We must give a chance to the theories to propagate before we create a new scope... propagate(); // Internal backtracking scopes (created with push_scope()) must only be created when we are @@ -3138,18 +3228,21 @@ namespace smt { if (get_cancel_flag()) return; push_scope(); - for (unsigned i = 0; i < num_assumptions; i++) { - expr * curr_assumption = assumptions[i]; + vector> asm2proxy; + internalize_proxies(asms, asm2proxy); + for (auto const& p: asm2proxy) { + expr_ref curr_assumption = p.second; + expr* orig_assumption = p.first; if (m_manager.is_true(curr_assumption)) continue; SASSERT(is_valid_assumption(m_manager, curr_assumption)); proof * pr = m_manager.mk_asserted(curr_assumption); internalize_assertion(curr_assumption, pr, 0); literal l = get_literal(curr_assumption); - m_literal2assumption.insert(l.index(), curr_assumption); + m_literal2assumption.insert(l.index(), orig_assumption); // mark_as_relevant(l); <<< not needed // internalize_assertion marked l as relevant. SASSERT(is_relevant(l)); - TRACE("assumptions", tout << l << ":" << mk_pp(curr_assumption, m_manager) << "\n";); + TRACE("assumptions", tout << l << ":" << curr_assumption << " " << mk_pp(orig_assumption, m_manager) << "\n";); if (m_manager.proofs_enabled()) assign(l, mk_justification(justification_proof_wrapper(*this, pr))); else @@ -3159,8 +3252,9 @@ namespace smt { } } m_search_lvl = m_scope_lvl; - SASSERT(!(num_assumptions > 0) || m_search_lvl > m_base_lvl); - SASSERT(!(num_assumptions == 0) || m_search_lvl == m_base_lvl); + SASSERT(asms.empty() || m_search_lvl > m_base_lvl); + SASSERT(!asms.empty() || m_search_lvl == m_base_lvl); + TRACE("after_internalization", display(tout);); } void context::reset_assumptions() { @@ -3169,7 +3263,8 @@ namespace smt { m_assumptions.reset(); } - lbool context::mk_unsat_core() { + lbool context::mk_unsat_core(lbool r) { + if (r != l_false) return r; SASSERT(inconsistent()); if (!tracking_assumptions()) { SASSERT(m_assumptions.empty()); @@ -3186,18 +3281,16 @@ namespace smt { SASSERT(m_literal2assumption.contains(l.index())); if (!already_found_assumptions.contains(l.index())) { already_found_assumptions.insert(l.index()); - m_unsat_core.push_back(m_literal2assumption[l.index()]); + expr* orig_assumption = m_literal2assumption[l.index()]; + m_unsat_core.push_back(orig_assumption); + TRACE("assumptions", tout << l << ": " << mk_pp(orig_assumption, m_manager) << "\n";); } } reset_assumptions(); pop_to_base_lvl(); // undo the push_scope() performed by init_assumptions m_search_lvl = m_base_lvl; std::sort(m_unsat_core.c_ptr(), m_unsat_core.c_ptr() + m_unsat_core.size(), ast_lt_proc()); - TRACE("unsat_core_bug", tout << "unsat core:\n"; - unsigned sz = m_unsat_core.size(); - for (unsigned i = 0; i < sz; i++) { - tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; - }); + TRACE("unsat_core_bug", tout << "unsat core:\n" << m_unsat_core << "\n";); validate_unsat_core(); // theory validation of unsat core for (theory* th : m_theory_set) { @@ -3221,6 +3314,10 @@ namespace smt { m_last_search_failure = MEMOUT; return false; } + reset_tmp_clauses(); + m_unsat_core.reset(); + m_stats.m_num_checks++; + pop_to_base_lvl(); return true; } @@ -3244,8 +3341,7 @@ namespace smt { and before internalizing any formulas. */ lbool context::setup_and_check(bool reset_cancel) { - if (!check_preamble(reset_cancel)) - return l_undef; + if (!check_preamble(reset_cancel)) return l_undef; SASSERT(m_scope_lvl == 0); SASSERT(!m_setup.already_configured()); setup_context(m_fparams.m_auto_config); @@ -3258,20 +3354,8 @@ namespace smt { } internalize_assertions(); - lbool r = l_undef; TRACE("before_search", display(tout);); - if (m_asserted_formulas.inconsistent()) { - r = l_false; - } - else { - if (inconsistent()) { - VERIFY(!resolve_conflict()); // build the proof - r = l_false; - } - else { - r = search(); - } - } + lbool r = search(); r = check_finalize(r); return r; } @@ -3285,7 +3369,7 @@ namespace smt { } void context::setup_context(bool use_static_features) { - if (m_setup.already_configured()) + if (m_setup.already_configured() || inconsistent()) return; m_setup(get_config_mode(use_static_features)); setup_components(); @@ -3320,78 +3404,45 @@ namespace smt { } } - lbool context::check(unsigned ext_num_assumptions, expr * const * ext_assumptions, bool reset_cancel, bool already_did_theory_assumptions) { - m_stats.m_num_checks++; - TRACE("check_bug", tout << "STARTING check(num_assumptions, assumptions)\n"; - tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n"; - m_asserted_formulas.display(tout); - tout << "-----------------------\n"; - display(tout);); - if (!m_unsat_core.empty()) - m_unsat_core.reset(); - if (!check_preamble(reset_cancel)) - return l_undef; - - TRACE("check_bug", tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n";); - pop_to_base_lvl(); - TRACE("before_search", display(tout);); + lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel, bool already_did_theory_assumptions) { + if (!check_preamble(reset_cancel)) return l_undef; SASSERT(at_base_level()); - lbool r = l_undef; - if (inconsistent()) { - r = l_false; - } - else { - setup_context(false); - expr_ref_vector all_assumptions(m_manager, ext_num_assumptions, ext_assumptions); - if (!already_did_theory_assumptions) { - add_theory_assumptions(all_assumptions); - } - - unsigned num_assumptions = all_assumptions.size(); - expr * const * assumptions = all_assumptions.c_ptr(); - - if (!validate_assumptions(num_assumptions, assumptions)) - return l_undef; - TRACE("unsat_core_bug", tout << all_assumptions << "\n";); - - internalize_assertions(); - TRACE("after_internalize_assertions", display(tout);); - if (m_asserted_formulas.inconsistent()) { - r = l_false; - } - else { - init_assumptions(num_assumptions, assumptions); - TRACE("after_internalization", display(tout);); - if (inconsistent()) { - VERIFY(!resolve_conflict()); // build the proof - lbool result = mk_unsat_core(); - if (result == l_undef) { - r = l_undef; - } else { - r = l_false; - } - } - else { - r = search(); - if (r == l_false) { - lbool result = mk_unsat_core(); - if (result == l_undef) { - r = l_undef; - } - } - } - } - } + setup_context(false); + expr_ref_vector asms(m_manager, num_assumptions, assumptions); + if (!already_did_theory_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 = search(); + r = mk_unsat_core(r); r = check_finalize(r); return r; } + lbool context::check(expr_ref_vector const& cube, vector const& clauses) { + 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 = search(); + r = mk_unsat_core(r); + r = check_finalize(r); + return r; + } + void context::init_search() { for (theory* th : m_theory_set) { th->init_search_eh(); } m_qmanager->init_search_eh(); - m_assumption_core.reset(); m_incomplete_theories.reset(); m_num_conflicts = 0; m_num_conflicts_since_restart = 0; @@ -3454,6 +3505,12 @@ namespace smt { exit(1); } #endif + if (m_asserted_formulas.inconsistent()) + return l_false; + if (inconsistent()) { + VERIFY(!resolve_conflict()); + return l_false; + } timeit tt(get_verbosity_level() >= 100, "smt.stats"); scoped_mk_model smk(*this); SASSERT(at_search_level()); @@ -3477,24 +3534,19 @@ namespace smt { if (!restart(status, curr_lvl)) { break; - } + } } - TRACE("search_lite", tout << "status: " << status << "\n";); TRACE("guessed_literals", expr_ref_vector guessed_lits(m_manager); get_guessed_literals(guessed_lits); - unsigned sz = guessed_lits.size(); - for (unsigned i = 0; i < sz; i++) { - tout << mk_pp(guessed_lits.get(i), m_manager) << "\n"; - }); + tout << guessed_lits << "\n";); end_search(); return status; } bool context::restart(lbool& status, unsigned curr_lvl) { - if (m_last_search_failure != OK) { if (status != l_false) { // build candidate model before returning @@ -3647,6 +3699,8 @@ namespace smt { simplify_clauses(); if (!decide()) { + if (inconsistent()) + return l_false; final_check_status fcs = final_check(); TRACE("final_check_result", tout << "fcs: " << fcs << " last_search_failure: " << m_last_search_failure << "\n";); switch (fcs) { @@ -3704,6 +3758,7 @@ namespace smt { TRACE("final_check", tout << "final_check inconsistent: " << inconsistent() << "\n"; display(tout); display_normalized_enodes(tout);); CASSERT("relevancy", check_relevancy()); + if (m_fparams.m_model_on_final_check) { mk_proto_model(l_undef); model_pp(std::cout, *m_proto_model); @@ -4232,7 +4287,7 @@ namespace smt { theory_var_list * l = n->get_th_var_list(); theory_id th_id = l->get_th_id(); - for (enode* parent : n->get_parents()) { + for (enode * parent : enode::parents(n)) { family_id fid = parent->get_owner()->get_family_id(); if (fid != th_id && fid != m_manager.get_basic_family_id()) { TRACE("is_shared", tout << mk_pp(n->get_owner(), m_manager) << "\nis shared because of:\n" << mk_pp(parent->get_owner(), m_manager) << "\n";); @@ -4313,7 +4368,7 @@ namespace smt { m_proto_model = m_model_generator->mk_model(); m_qmanager->adjust_model(m_proto_model.get()); TRACE("mbqi_bug", tout << "before complete_partial_funcs:\n"; model_pp(tout, *m_proto_model);); - m_proto_model->complete_partial_funcs(); + m_proto_model->complete_partial_funcs(false); TRACE("mbqi_bug", tout << "before cleanup:\n"; model_pp(tout, *m_proto_model);); m_proto_model->cleanup(); if (m_fparams.m_model_compact) diff --git a/src/smt/smt_context.h b/src/smt/smt_context.h index 106f05112..45d5f15d3 100644 --- a/src/smt/smt_context.h +++ b/src/smt/smt_context.h @@ -169,6 +169,8 @@ namespace smt { expr_ref_vector m_units_to_reassert; svector m_units_to_reassert_sign; literal_vector m_assigned_literals; + typedef std::pair tmp_clause; + vector m_tmp_clauses; unsigned m_qhead; unsigned m_simp_qhead; int m_simp_counter; //!< can become negative @@ -893,7 +895,6 @@ namespace smt { failure m_last_search_failure; ptr_vector m_incomplete_theories; //!< theories that failed to produce a model bool m_searching; - ptr_vector m_assumption_core; unsigned m_num_conflicts; unsigned m_num_conflicts_since_restart; unsigned m_num_conflicts_since_lemma_gc; @@ -1105,15 +1106,23 @@ namespace smt { void assert_assumption(expr * a); - bool validate_assumptions(unsigned num_assumptions, expr * const * assumptions); + bool validate_assumptions(expr_ref_vector const& asms); - void init_assumptions(unsigned num_assumptions, expr * const * assumptions); + void init_assumptions(expr_ref_vector const& asms); + + void init_clause(expr_ref_vector const& clause); + + lbool decide_clause(); + + void reset_tmp_clauses(); void reset_assumptions(); + void reset_clause(); + void add_theory_assumptions(expr_ref_vector & theory_assumptions); - lbool mk_unsat_core(); + lbool mk_unsat_core(lbool result); void validate_unsat_core(); @@ -1498,6 +1507,8 @@ namespace smt { lbool check(unsigned num_assumptions = 0, expr * const * assumptions = nullptr, bool reset_cancel = true, bool already_did_theory_assumptions = false); + lbool check(expr_ref_vector const& cube, vector const& clauses); + lbool get_consequences(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq, expr_ref_vector& unfixed); lbool find_mutexes(expr_ref_vector const& vars, vector& mutexes); @@ -1525,6 +1536,8 @@ namespace smt { void internalize_assertion(expr * n, proof * pr, unsigned generation); + void internalize_proxies(expr_ref_vector const& asms, vector>& asm2proxy); + void internalize_instance(expr * body, proof * pr, unsigned generation) { internalize_assertion(body, pr, generation); if (relevancy()) diff --git a/src/smt/smt_context_inv.cpp b/src/smt/smt_context_inv.cpp index cf09c996a..7f409622b 100644 --- a/src/smt/smt_context_inv.cpp +++ b/src/smt/smt_context_inv.cpp @@ -43,13 +43,9 @@ namespace smt { } bool context::check_clauses(clause_vector const & v) const { - clause_vector::const_iterator it = v.begin(); - clause_vector::const_iterator end = v.end(); - for (; it != end; ++it) { - clause * cls = *it; + for (clause* cls : v) if (!cls->deleted()) check_clause(cls); - } return true; } @@ -92,10 +88,7 @@ namespace smt { bool context::check_lit_occs(literal l) const { clause_set const & v = m_lit_occs[l.index()]; - clause_set::iterator it = v.begin(); - clause_set::iterator end = v.end(); - for (; it != end; ++it) { - clause * cls = *it; + for (clause * cls : v) { unsigned num = cls->get_num_literals(); unsigned i = 0; for (; i < num; i++) @@ -138,10 +131,8 @@ namespace smt { } bool context::check_enodes() const { - ptr_vector::const_iterator it = m_enodes.begin(); - ptr_vector::const_iterator end = m_enodes.end(); - for (; it != end; ++it) { - check_enode(*it); + for (enode* n : m_enodes) { + check_enode(n); } return true; } @@ -157,11 +148,9 @@ namespace smt { } bool context::check_missing_clause_propagation(clause_vector const & v) const { - clause_vector::const_iterator it = v.begin(); - clause_vector::const_iterator end = v.end(); - for (; it != end; ++it) { - CTRACE("missing_propagation", is_unit_clause(*it), display_clause_detail(tout, *it); tout << "\n";); - SASSERT(!is_unit_clause(*it)); + for (clause * cls : v) { + CTRACE("missing_propagation", is_unit_clause(cls), display_clause_detail(tout, cls); tout << "\n";); + SASSERT(!is_unit_clause(cls)); } return true; } @@ -188,10 +177,7 @@ namespace smt { } bool context::check_missing_eq_propagation() const { - ptr_vector::const_iterator it = m_enodes.begin(); - ptr_vector::const_iterator end = m_enodes.end(); - for (; it != end; ++it) { - enode * n = *it; + for (enode* n : m_enodes) { SASSERT(!n->is_true_eq() || get_assignment(n) == l_true); if (n->is_eq() && get_assignment(n) == l_true) { SASSERT(n->is_true_eq()); @@ -201,13 +187,8 @@ namespace smt { } bool context::check_missing_congruence() const { - ptr_vector::const_iterator it = m_enodes.begin(); - ptr_vector::const_iterator end = m_enodes.end(); - for (; it != end; ++it) { - enode * n = *it; - ptr_vector::const_iterator it2 = m_enodes.begin(); - for (; it2 != end; ++it2) { - enode * n2 = *it2; + for (enode* n : m_enodes) { + for (enode* n2 : m_enodes) { if (n->get_root() != n2->get_root()) { if (n->is_true_eq() && n2->is_true_eq()) continue; @@ -222,10 +203,7 @@ namespace smt { } bool context::check_missing_bool_enode_propagation() const { - ptr_vector::const_iterator it = m_enodes.begin(); - ptr_vector::const_iterator end = m_enodes.end(); - for (; it != end; ++it) { - enode * n = *it; + for (enode* n : m_enodes) { if (m_manager.is_bool(n->get_owner()) && get_assignment(n) == l_undef) { enode * first = n; do { @@ -286,10 +264,7 @@ namespace smt { For all a, b. root(a) == root(b) ==> get_assignment(a) == get_assignment(b) */ bool context::check_eqc_bool_assignment() const { - ptr_vector::const_iterator it = m_enodes.begin(); - ptr_vector::const_iterator end = m_enodes.end(); - for (; it != end; ++it) { - enode * e = *it; + for (enode* e : m_enodes) { if (m_manager.is_bool(e->get_owner())) { enode * r = e->get_root(); CTRACE("eqc_bool", get_assignment(e) != get_assignment(r), @@ -343,24 +318,24 @@ namespace smt { TRACE("check_th_diseq_propagation", tout << "checking theory: " << m_manager.get_family_name(th_id) << "\n";); // if the theory doesn't use diseqs, then the diseqs are not propagated. if (th->use_diseqs() && rhs->get_th_var(th_id) != null_theory_var) { + bool found = false; // lhs and rhs are attached to theory th_id - svector::const_iterator it = m_propagated_th_diseqs.begin(); - svector::const_iterator end = m_propagated_th_diseqs.end(); - for (; it != end; ++it) { - if (it->m_th_id == th_id) { - enode * lhs_prime = th->get_enode(it->m_lhs)->get_root(); - enode * rhs_prime = th->get_enode(it->m_rhs)->get_root(); + for (new_th_eq const& eq : m_propagated_th_diseqs) { + if (eq.m_th_id == th_id) { + enode * lhs_prime = th->get_enode(eq.m_lhs)->get_root(); + enode * rhs_prime = th->get_enode(eq.m_rhs)->get_root(); TRACE("check_th_diseq_propagation", - tout << m_manager.get_family_name(it->m_th_id) << "\n";); + tout << m_manager.get_family_name(eq.m_th_id) << "\n";); if ((lhs == lhs_prime && rhs == rhs_prime) || (rhs == lhs_prime && lhs == rhs_prime)) { TRACE("check_th_diseq_propagation", tout << "ok v" << v << " " << get_assignment(v) << "\n";); + found = true; break; } } } - if (it == end) { + if (!found) { // missed theory diseq propagation display(std::cout); std::cout << "checking theory: " << m_manager.get_family_name(th_id) << "\n"; @@ -369,8 +344,7 @@ namespace smt { std::cout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() << "\n"; std::cout << mk_bounded_pp(lhs->get_owner(), m_manager) << " " << mk_bounded_pp(rhs->get_owner(), m_manager) << "\n"; } - - SASSERT(it != end); + VERIFY(found); } l = l->get_next(); } @@ -381,11 +355,9 @@ namespace smt { } bool context::check_missing_diseq_conflict() const { - svector::const_iterator it = m_diseq_vector.begin(); - svector::const_iterator end = m_diseq_vector.end(); - for (; it != end; ++it) { - enode * n1 = it->first; - enode * n2 = it->second; + for (enode_pair const& p : m_diseq_vector) { + enode * n1 = p.first; + enode * n2 = p.second; if (n1->get_root() == n2->get_root()) { TRACE("diseq_bug", tout << "n1: #" << n1->get_owner_id() << ", n2: #" << n2->get_owner_id() << @@ -420,10 +392,7 @@ namespace smt { return true; } ast_manager& m = m_manager; - literal_vector::const_iterator it = m_assigned_literals.begin(); - literal_vector::const_iterator end = m_assigned_literals.end(); - for (; it != end; ++it) { - literal lit = *it; + for (literal lit : m_assigned_literals) { if (!is_relevant(lit)) { continue; } @@ -435,7 +404,7 @@ namespace smt { if (is_quantifier(n) && m.is_rec_fun_def(to_quantifier(n))) { continue; } - switch (get_assignment(*it)) { + switch (get_assignment(lit)) { case l_undef: break; case l_true: diff --git a/src/smt/smt_context_pp.cpp b/src/smt/smt_context_pp.cpp index b8439c8ed..6f971acc8 100644 --- a/src/smt/smt_context_pp.cpp +++ b/src/smt/smt_context_pp.cpp @@ -583,7 +583,7 @@ namespace smt { case b_justification::CLAUSE: { clause * cls = j.get_clause(); out << "clause "; - if (cls) out << literal_vector(cls->get_num_literals(), cls->begin_literals()); + if (cls) out << literal_vector(cls->get_num_literals(), cls->begin()); break; } case b_justification::JUSTIFICATION: { diff --git a/src/smt/smt_implied_equalities.cpp b/src/smt/smt_implied_equalities.cpp index 0b944c145..9119119d3 100644 --- a/src/smt/smt_implied_equalities.cpp +++ b/src/smt/smt_implied_equalities.cpp @@ -199,7 +199,7 @@ namespace smt { for (unsigned i = 0; i < terms.size(); ++i) { expr* t = terms[i].term; - model->eval(t, vl); + vl = (*model)(t); TRACE("get_implied_equalities", tout << mk_pp(t, m) << " |-> " << mk_pp(vl, m) << "\n";); reduce_value(model, vl); if (!m.is_value(vl)) { diff --git a/src/smt/smt_internalizer.cpp b/src/smt/smt_internalizer.cpp index dc7c32cc1..f9ee900ff 100644 --- a/src/smt/smt_internalizer.cpp +++ b/src/smt/smt_internalizer.cpp @@ -34,9 +34,10 @@ namespace smt { switch (to_app(n)->get_decl_kind()) { case OP_AND: case OP_OR: - case OP_IFF: case OP_ITE: return true; + case OP_EQ: + return m.is_bool(to_app(n)->get_arg(0)); default: return false; } @@ -229,7 +230,7 @@ namespace smt { add_or_rel_watches(to_app(n)); break; } - case OP_IFF: { + case OP_EQ: { expr * lhs = to_app(n)->get_arg(0); expr * rhs = to_app(n)->get_arg(1); internalize(lhs, true); @@ -381,7 +382,7 @@ namespace smt { return; } - if (m_manager.is_eq(n)) + if (m_manager.is_eq(n) && !m_manager.is_iff(n)) internalize_eq(to_app(n), gate_ctx); else if (m_manager.is_distinct(n)) internalize_distinct(to_app(n), gate_ctx); @@ -538,9 +539,7 @@ namespace smt { bool _is_gate = is_gate(m_manager, n) || m_manager.is_not(n); // process args - unsigned num = n->get_num_args(); - for (unsigned i = 0; i < num; i++) { - expr * arg = n->get_arg(i); + for (expr * arg : *n) { internalize(arg, _is_gate); } @@ -596,8 +595,9 @@ namespace smt { mk_or_cnstr(to_app(n)); add_or_rel_watches(to_app(n)); break; - case OP_IFF: - mk_iff_cnstr(to_app(n)); + case OP_EQ: + if (m_manager.is_iff(n)) + mk_iff_cnstr(to_app(n)); break; case OP_ITE: mk_ite_cnstr(to_app(n)); diff --git a/src/smt/smt_kernel.cpp b/src/smt/smt_kernel.cpp index a6413aef9..b03604b5b 100644 --- a/src/smt/smt_kernel.cpp +++ b/src/smt/smt_kernel.cpp @@ -115,6 +115,10 @@ namespace smt { return m_kernel.check(num_assumptions, assumptions); } + lbool check(expr_ref_vector const& cube, vector const& clause) { + return m_kernel.check(cube, clause); + } + lbool get_consequences(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq, expr_ref_vector& unfixed) { return m_kernel.get_consequences(assumptions, vars, conseq, unfixed); } @@ -287,6 +291,11 @@ namespace smt { return r; } + lbool kernel::check(expr_ref_vector const& cube, vector const& clauses) { + return m_imp->check(cube, clauses); + } + + lbool kernel::get_consequences(expr_ref_vector const& assumptions, expr_ref_vector const& vars, expr_ref_vector& conseq, expr_ref_vector& unfixed) { return m_imp->get_consequences(assumptions, vars, conseq, unfixed); } diff --git a/src/smt/smt_kernel.h b/src/smt/smt_kernel.h index 7b2f774ad..d78f71e20 100644 --- a/src/smt/smt_kernel.h +++ b/src/smt/smt_kernel.h @@ -132,6 +132,8 @@ namespace smt { lbool check(app_ref_vector const& asms) { return check(asms.size(), (expr* const*)asms.c_ptr()); } + lbool check(expr_ref_vector const& cube, vector const& clauses); + /** \brief extract consequences among variables. */ diff --git a/src/smt/smt_model_checker.cpp b/src/smt/smt_model_checker.cpp index 549c15148..7dde3bb00 100644 --- a/src/smt/smt_model_checker.cpp +++ b/src/smt/smt_model_checker.cpp @@ -439,7 +439,7 @@ namespace smt { } else if (!check(q)) { if (m_params.m_mbqi_trace || get_verbosity_level() >= 5) { - verbose_stream() << "(smt.mbqi :failed " << q->get_qid() << ")\n"; + IF_VERBOSE(0, verbose_stream() << "(smt.mbqi :failed " << q->get_qid() << ")\n"); } TRACE("model_checker", tout << "checking quantifier " << mk_pp(q, m) << " failed\n";); num_failures++; diff --git a/src/smt/smt_model_finder.cpp b/src/smt/smt_model_finder.cpp index 5e21c0dcc..73f85faf6 100644 --- a/src/smt/smt_model_finder.cpp +++ b/src/smt/smt_model_finder.cpp @@ -1028,7 +1028,7 @@ namespace smt { void complete_partial_funcs(func_decl_set const & partial_funcs) { for (func_decl * f : partial_funcs) { // Complete the current interpretation - m_model->complete_partial_func(f); + m_model->complete_partial_func(f, true); unsigned arity = f->get_arity(); func_interp * fi = m_model->get_func_interp(f); @@ -2315,9 +2315,6 @@ namespace smt { case OP_ITE: process_ite(to_app(curr), pol); break; - case OP_IFF: - process_iff(to_app(curr)); - break; case OP_EQ: if (m_manager.is_bool(to_app(curr)->get_arg(0))) { process_iff(to_app(curr)); diff --git a/src/smt/smt_quantifier_stat.cpp b/src/smt/smt_quantifier_stat.cpp index 7d73ea27c..8c7fd196e 100644 --- a/src/smt/smt_quantifier_stat.cpp +++ b/src/smt/smt_quantifier_stat.cpp @@ -82,11 +82,13 @@ namespace smt { if (depth > 0) m_case_split_factor *= (num_args + 1); break; - case OP_IFF: - if (depth == 0) - m_case_split_factor *= 4; - else - m_case_split_factor *= 9; + case OP_EQ: + if (m_manager.is_iff(n)) { + if (depth == 0) + m_case_split_factor *= 4; + else + m_case_split_factor *= 9; + } break; case OP_ITE: if (depth == 0) diff --git a/src/smt/smt_quick_checker.cpp b/src/smt/smt_quick_checker.cpp index ab75d2dea..d549d7c68 100644 --- a/src/smt/smt_quick_checker.cpp +++ b/src/smt/smt_quick_checker.cpp @@ -312,11 +312,6 @@ namespace smt { return is_true ? any_arg(a, true) : all_args(a, false); case OP_AND: return is_true ? all_args(a, true) : any_arg(a, false); - case OP_IFF: - if (is_true) - return (check(a->get_arg(0), true) && check(a->get_arg(1), true)) || (check(a->get_arg(0), false) && check(a->get_arg(1), false)); - else - return (check(a->get_arg(0), true) && check(a->get_arg(1), false)) || (check(a->get_arg(0), false) && check(a->get_arg(1), true)); case OP_ITE: if (check(a->get_arg(0), true)) return check(a->get_arg(1), is_true); @@ -325,6 +320,13 @@ namespace smt { else return check(a->get_arg(1), is_true) && check(a->get_arg(2), is_true); case OP_EQ: + if (m_manager.is_iff(a)) { + if (is_true) + return (check(a->get_arg(0), true) && check(a->get_arg(1), true)) || (check(a->get_arg(0), false) && check(a->get_arg(1), false)); + else + return (check(a->get_arg(0), true) && check(a->get_arg(1), false)) || (check(a->get_arg(0), false) && check(a->get_arg(1), true)); + } + if (is_true) { return canonize(a->get_arg(0)) == canonize(a->get_arg(1)); } diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index 83b15fd90..b90f08cd3 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -574,19 +574,19 @@ namespace smt { m_params.m_bv_cc = false; m_params.m_bb_ext_gates = true; m_params.m_nnf_cnf = false; - m_params.m_propagate_booleans = true; m_context.register_plugin(alloc(smt::theory_bv, m_manager, m_params, m_params)); - m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + setup_arrays(); } void setup::setup_QF_AX() { + TRACE("setup", tout << "QF_AX\n";); m_params.m_array_mode = AR_SIMPLE; m_params.m_nnf_cnf = false; - m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + setup_arrays(); } void setup::setup_QF_AX(static_features const & st) { - m_params.m_array_mode = AR_SIMPLE; + m_params.m_array_mode = st.m_has_ext_arrays ? AR_FULL : AR_SIMPLE; m_params.m_nnf_cnf = false; if (st.m_num_clauses == st.m_num_units) { m_params.m_relevancy_lvl = 0; @@ -595,7 +595,7 @@ namespace smt { else { m_params.m_relevancy_lvl = 2; } - m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + setup_arrays(); } void setup::setup_QF_AUFLIA() { @@ -607,11 +607,11 @@ namespace smt { m_params.m_restart_factor = 1.5; m_params.m_phase_selection = PS_CACHING_CONSERVATIVE2; setup_i_arith(); - m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + setup_arrays(); } void setup::setup_QF_AUFLIA(static_features const & st) { - m_params.m_array_mode = AR_SIMPLE; + m_params.m_array_mode = st.m_has_ext_arrays ? AR_FULL : AR_SIMPLE; if (st.m_has_real) throw default_exception("Benchmark has real variables but it is marked as QF_AUFLIA (arrays, uninterpreted functions and linear integer arithmetic)."); m_params.m_nnf_cnf = false; @@ -631,7 +631,7 @@ namespace smt { // m_context.register_plugin(new smt::theory_si_arith(m_manager, m_params)); // else setup_i_arith(); - m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + setup_arrays(); } void setup::setup_AUFLIA(bool simple_array) { @@ -643,7 +643,6 @@ namespace smt { m_params.m_restart_factor = 1.5; m_params.m_eliminate_bounds = true; m_params.m_qi_quick_checker = MC_UNSAT; - m_params.m_propagate_booleans = true; m_params.m_qi_lazy_threshold = 20; // m_params.m_qi_max_eager_multipatterns = 10; /// <<< HACK m_params.m_mbqi = true; // enabling MBQI and MACRO_FINDER by default :-) @@ -671,7 +670,6 @@ namespace smt { m_params.m_phase_selection = PS_ALWAYS_FALSE; m_params.m_eliminate_bounds = true; m_params.m_qi_quick_checker = MC_UNSAT; - m_params.m_propagate_booleans = true; m_params.m_qi_eager_threshold = 5; // Added for MBQI release m_params.m_qi_lazy_threshold = 20; @@ -732,6 +730,7 @@ namespace smt { } void setup::setup_i_arith() { + // m_context.register_plugin(alloc(smt::theory_lra, m_manager, m_params)); m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); } @@ -992,17 +991,17 @@ namespace smt { } if (st.num_theories() == 1 && st.m_has_arrays) { - setup_QF_AX(); + setup_QF_AX(st); return; } - if (st.num_theories() == 2 && st.has_uf() && st.m_has_arrays && st.m_has_bv) { + if (st.num_theories() == 2 && st.has_uf() && st.m_has_arrays && !st.m_has_ext_arrays && st.m_has_bv) { setup_QF_AUFBV(); return; } if (st.num_theories() == 2 && st.has_uf() && st.m_has_arrays && st.m_has_int) { - setup_QF_AUFLIA(); + setup_QF_AUFLIA(st); return; } diff --git a/src/smt/smt_solver.cpp b/src/smt/smt_solver.cpp index d282a59da..48f6053fc 100644 --- a/src/smt/smt_solver.cpp +++ b/src/smt/smt_solver.cpp @@ -110,14 +110,28 @@ namespace smt { void updt_params(params_ref const & p) override { solver::updt_params(p); - m_smt_params.updt_params(p); - m_context.updt_params(p); - smt_params_helper smth(p); + m_smt_params.updt_params(solver::get_params()); + m_context.updt_params(solver::get_params()); + smt_params_helper smth(solver::get_params()); m_core_extend_patterns = smth.core_extend_patterns(); m_core_extend_patterns_max_distance = smth.core_extend_patterns_max_distance(); m_core_extend_nonlocal_patterns = smth.core_extend_nonlocal_patterns(); } + params_ref m_params_save; + smt_params m_smt_params_save; + + void push_params() override { + m_params_save = params_ref(); + m_params_save.copy(solver::get_params()); + m_smt_params_save = m_smt_params; + } + + void pop_params() override { + m_smt_params = m_smt_params_save; + solver::reset_params(m_params_save); + } + void collect_param_descrs(param_descrs & r) override { m_context.collect_param_descrs(r); } @@ -176,6 +190,11 @@ namespace smt { return m_context.check(num_assumptions, assumptions); } + + lbool check_sat_cc_core(expr_ref_vector const& cube, vector const& clauses) override { + return m_context.check(cube, clauses); + } + struct scoped_minimize_core { smt_solver& s; expr_ref_vector m_assumptions; @@ -190,17 +209,17 @@ namespace smt { } }; - void get_unsat_core(ptr_vector & r) override { + void get_unsat_core(expr_ref_vector & r) override { unsigned sz = m_context.get_unsat_core_size(); for (unsigned i = 0; i < sz; i++) { r.push_back(m_context.get_unsat_core_expr(i)); } - if (m_minimizing_core && smt_params_helper(get_params()).core_minimize()) { + if (!m_minimizing_core && smt_params_helper(get_params()).core_minimize()) { scoped_minimize_core scm(*this); mus mus(*this); mus.add_soft(r.size(), r.c_ptr()); - ptr_vector r2; + expr_ref_vector r2(m); if (l_true == mus.get_mus(r2)) { r.reset(); r.append(r2); @@ -314,7 +333,7 @@ namespace smt { for_each_expr(p, visited, e); } - void compute_assrtn_fds(ptr_vector & core, vector & assrtn_fds) { + void compute_assrtn_fds(expr_ref_vector & core, vector & assrtn_fds) { assrtn_fds.resize(m_name2assertion.size()); unsigned i = 0; for (auto & kv : m_name2assertion) { @@ -335,7 +354,7 @@ namespace smt { return false; } - void add_pattern_literals_to_core(ptr_vector & core) { + void add_pattern_literals_to_core(expr_ref_vector & core) { ast_manager & m = get_manager(); expr_ref_vector new_core_literals(m); @@ -394,7 +413,7 @@ namespace smt { for_each_expr(p, visited, e); } - void add_nonlocal_pattern_literals_to_core(ptr_vector & core) { + void add_nonlocal_pattern_literals_to_core(expr_ref_vector & core) { ast_manager & m = get_manager(); for (auto const& kv : m_name2assertion) { expr_ref name(kv.m_key, m); @@ -406,8 +425,8 @@ namespace smt { collect_body_func_decls(assrtn, body_fds); for (func_decl *fd : pattern_fds) { - if (!body_fds.contains(fd)) { - core.insert(name); + if (!body_fds.contains(fd) && !core.contains(name)) { + core.push_back(name); break; } } diff --git a/src/smt/theory_arith_core.h b/src/smt/theory_arith_core.h index 46388fcf4..cbd41f08c 100644 --- a/src/smt/theory_arith_core.h +++ b/src/smt/theory_arith_core.h @@ -27,7 +27,7 @@ Revision History: #include "ast/ast_smt2_pp.h" namespace smt { - + template void theory_arith::found_unsupported_op(app * n) { if (!m_found_unsupported_op) { @@ -69,33 +69,7 @@ namespace smt { #if 0 return m_util.is_int(e); #else - if (m_util.is_int(e)) return true; - if (is_uninterp(e)) return false; - m_todo.reset(); - m_todo.push_back(e); - rational r; - unsigned i = 0; - while (!m_todo.empty()) { - ++i; - if (i > 100) { - return false; - } - e = m_todo.back(); - m_todo.pop_back(); - if (m_util.is_to_real(e)) { - // pass - } - else if (m_util.is_numeral(e, r) && r.is_int()) { - // pass - } - else if (m_util.is_add(e) || m_util.is_mul(e)) { - m_todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); - } - else { - return false; - } - } - return true; + return m_util.is_int_expr(e); #endif } @@ -133,7 +107,7 @@ namespace smt { m_nl_monomials.push_back(r); SASSERT(check_vector_sizes()); SASSERT(m_var_occs[r].empty()); - TRACE("mk_arith_var", + TRACE("mk_arith_var", tout << "#" << n->get_owner_id() << " :=\n" << mk_ll_pp(n->get_owner(), get_manager()) << "\n"; tout << "is_attached_to_var: " << is_attached_to_var(n) << ", var: " << n->get_th_var(get_id()) << "\n";); get_context().attach_th_var(n, this, r); @@ -175,15 +149,15 @@ namespace smt { } /** - \brief Create an enode for n. + \brief Create an enode for n. */ template enode * theory_arith::mk_enode(app * n) { context & ctx = get_context(); - if (ctx.e_internalized(n)) + if (ctx.e_internalized(n)) return ctx.get_enode(n); else - return ctx.mk_enode(n, !reflect(n), false, enable_cgc_for(n)); + return ctx.mk_enode(n, !reflect(n), false, enable_cgc_for(n)); } /** @@ -239,13 +213,13 @@ namespace smt { row_entry & r_entry = r.add_row_entry(r_idx); int c_idx; col_entry & c_entry = c.add_col_entry(c_idx); - + r_entry.m_var = v; r_entry.m_coeff = coeff; if (invert) r_entry.m_coeff .neg(); r_entry.m_col_idx = c_idx; - + c_entry.m_row_id = r_id; c_entry.m_row_idx = r_idx; } @@ -266,7 +240,7 @@ namespace smt { return; } } - rational _val; + rational _val; expr* arg1, *arg2; if (m_util.is_mul(m, arg1, arg2) && m_util.is_numeral(arg1, _val) && is_app(arg1) && is_app(arg2)) { SASSERT(m->get_num_args() == 2); @@ -315,7 +289,7 @@ namespace smt { // HACK: n was already internalized by the internalize_internal_monomial or internalize_internal_add call above. // This can happen when one of calls invoke (indirectly) mk_axiom. // For example, they contain a nested to_int(t) term. - // TODO: reimplement mk_axiom. The current implementation is flaky. + // TODO: reimplement mk_axiom. The current implementation is flaky. // I should cache the axioms that need to be created. They should only be internalized after we finished internalizing the // current atom. Several other theories have similar problems. del_row(r_id); @@ -348,7 +322,7 @@ namespace smt { } /** - \brief Internalize the terms of the form (* c (* t1 ... tn)) and (* t1 ... tn). + \brief Internalize the terms of the form (* c (* t1 ... tn)) and (* t1 ... tn). Return an alias for the term. */ template @@ -389,7 +363,7 @@ namespace smt { return expr2var(n); ctx.internalize(n->get_arg(0), false); ctx.internalize(n->get_arg(1), false); - enode * e = mk_enode(n); + enode * e = mk_enode(n); return mk_var(e); } @@ -479,7 +453,7 @@ namespace smt { ctx.mark_as_relevant(l_conseq); } else { - // We must mark the antecedent as relevant, otherwise the + // We must mark the antecedent as relevant, otherwise the // core will not propagate it to the theory of arithmetic. // In a previous version, we were not doing that. // The core was assigning it to true, this assignment was inconsistent with @@ -496,7 +470,7 @@ namespace smt { if (!m_util.is_zero(q)) { ast_manager & m = get_manager(); expr_ref div(m), zero(m), eqz(m), eq(m); - TRACE("div_axiom_bug", tout << "expanding div_axiom for: " << mk_pp(p, m) << " / " << mk_pp(q, m) << "\n";); + TRACE("div_axiom_bug", tout << "expanding div_axiom for: " << mk_pp(p, m) << " / " << mk_pp(q, m) << "\n";); div = m_util.mk_div(p, q); zero = m_util.mk_numeral(rational(0), false); eqz = m.mk_eq(q, zero); @@ -525,7 +499,7 @@ namespace smt { eq = m.mk_eq(m_util.mk_add(m_util.mk_mul(divisor, div), mod), dividend); lower = m_util.mk_ge(mod, zero); upper = m_util.mk_le(mod, abs_divisor); - TRACE("div_axiom_bug", + TRACE("div_axiom_bug", tout << "eqz: " << eqz << " neq: " << eq << "\n"; tout << "lower: " << lower << "\n"; tout << "upper: " << upper << "\n";); @@ -534,13 +508,14 @@ namespace smt { mk_axiom(eqz, lower, !is_numeral); mk_axiom(eqz, upper, !is_numeral); rational k; - if (m_params.m_arith_enum_const_mod && m_util.is_numeral(divisor, k) && + context& ctx = get_context(); + (void)ctx; + if (m_params.m_arith_enum_const_mod && m_util.is_numeral(divisor, k) && k.is_pos() && k < rational(8)) { rational j(0); #if 1 literal_buffer lits; expr_ref mod_j(m); - context& ctx = get_context(); while(j < k) { mod_j = m.mk_eq(mod, m_util.mk_numeral(j, true)); ctx.internalize(mod_j, false); @@ -550,7 +525,7 @@ namespace smt { j += rational(1); } ctx.mk_th_axiom(get_id(), lits.size(), lits.begin()); - + #else // performs slightly worse. literal_buffer lits; @@ -567,7 +542,37 @@ namespace smt { j += rational(1); } #endif - } + } +#if 0 + // e-matching is too restrictive for multiplication. + // also suffers from use-after free so formulas have to be pinned in solver. + // + if (!m_util.is_numeral(divisor)) { + // + // forall x . (or (= y 0) (= (div (* x y) y) x)) + // forall x . (=> (= y 0) (= (div (* x y) y) (div 0 0))) + // + sort* intS = m_util.mk_int(); + var_ref v(m.mk_var(0, intS), m); + app_ref mul(m_util.mk_mul(divisor, v), m); + app_ref div(m_util.mk_idiv(mul, divisor), m); + expr_ref divp1(m.mk_pattern(div), m); + app_ref mul2(m_util.mk_mul(v, divisor), m); + app_ref div2(m_util.mk_idiv(mul2, divisor), m); + expr_ref divp2(m.mk_pattern(div2), m); + expr_ref fml1(m.mk_or(m.mk_not(eqz), m.mk_eq(div, m_util.mk_idiv(zero, zero))), m); + expr_ref fml2(m.mk_or(eqz, m.mk_eq(div, v)), m); + symbol name("?x"); + expr* pats[2] = { divp1, divp2 }; + expr_ref fml(m); + fml = m.mk_forall(1, &intS, &name, fml1, 0, symbol::null, symbol::null, 2, pats, 0, nullptr); + proof_ref pr(m.mk_asserted(fml), m); + ctx.internalize_assertion(fml, pr, 0); + fml = m.mk_forall(1, &intS, &name, fml2, 0, symbol::null, symbol::null, 2, pats, 0, nullptr); + pr = m.mk_asserted(fml); + ctx.internalize_assertion(fml, pr, 0); + } +#endif } } @@ -586,12 +591,12 @@ namespace smt { mk_axiom(dltz, eq1); dltz = m.mk_not(dltz); // !n < 0 || rem(a,n) = -mod(a, n) - mk_axiom(dltz, eq2); + mk_axiom(dltz, eq2); } // // create the term: s := x - to_real(to_int(x)) - // add the bounds 0 <= s < 1 + // add the bounds 0 <= s < 1 // template void theory_arith::mk_to_int_axiom(app * n) { @@ -606,7 +611,7 @@ namespace smt { } expr_ref to_r(m_util.mk_to_real(n), m); expr_ref diff(m_util.mk_add(x, m_util.mk_mul(m_util.mk_real(-1), to_r)), m); - + expr_ref lo(m_util.mk_ge(diff, m_util.mk_real(0)), m); expr_ref hi(m_util.mk_ge(diff, m_util.mk_real(1)), m); hi = m.mk_not(hi); @@ -631,7 +636,7 @@ namespace smt { // // Create the axiom (iff (is_int x) (= x (to_real (to_int x)))) - // + // template void theory_arith::mk_is_int_axiom(app * n) { @@ -718,7 +723,7 @@ namespace smt { ++m_top; } ~scoped_row_vars() { - --m_top; + --m_top; } }; @@ -738,7 +743,7 @@ namespace smt { SASSERT(!m_util.is_sub(n)); SASSERT(!m_util.is_uminus(n)); - + if (m_util.is_add(n)) return internalize_add(n); else if (m_util.is_mul(n)) @@ -747,9 +752,9 @@ namespace smt { return internalize_div(n); else if (m_util.is_idiv(n)) return internalize_idiv(n); - else if (m_util.is_mod(n)) + else if (m_util.is_mod(n)) return internalize_mod(n); - else if (m_util.is_rem(n)) + else if (m_util.is_rem(n)) return internalize_rem(n); else if (m_util.is_to_real(n)) return internalize_to_real(n); @@ -844,7 +849,7 @@ namespace smt { /** \brief Collect variables in the given row that have the given kind, but a different from the row main var (i.e., var that owns the row). - + The inv of the coefficients is also stored in result */ template @@ -864,7 +869,7 @@ namespace smt { } /** - \brief Normalize row as a quasi base row, it does not contain quasi-base + \brief Normalize row as a quasi base row, it does not contain quasi-base variables different from r.m_base_var. */ template @@ -910,7 +915,7 @@ namespace smt { // For example, consider the following scenario: // // 1) s is a quasi-base var, s depends on x, and value of x is v0 - // + // // 2) x is updated to v1, but the update does not affect s (s is a quasi-base var). // // 3) quasi_base_row2base_row is executed, and we compute the value of s using @@ -920,7 +925,7 @@ namespace smt { // // 5) if this branch is deleted, the row owned by s will not satisfy // valid_row_assignment. - // + // m_value[s] = tmp; SASSERT(!m_in_update_trail_stack.contains(s)); save_value(s); @@ -962,8 +967,8 @@ namespace smt { TRACE("mk_bound_axioms", tout << "add bound axioms for v" << v << " " << a1 << "\n";); if (!get_context().is_searching()) { // - // NB. We make an assumption that user push calls propagation - // before internal scopes are pushed. This flushes all newly + // NB. We make an assumption that user push calls propagation + // before internal scopes are pushed. This flushes all newly // asserted atoms into the right context. // m_new_atoms.push_back(a1); @@ -978,7 +983,7 @@ namespace smt { typename atoms::iterator lo_inf = end, lo_sup = end; typename atoms::iterator hi_inf = end, hi_sup = end; for (; it != end; ++it) { - atom * a2 = *it; + atom * a2 = *it; inf_numeral const & k2(a2->get_k()); atom_kind kind2 = a2->get_atom_kind(); TRACE("mk_bound_axioms", display_atom(tout << "compare " << a2 << " ", a2, true); tout << "\n";); @@ -1006,7 +1011,7 @@ namespace smt { else if (hi_sup == end || k2 < (*hi_sup)->get_k()) { hi_sup = it; } - } + } if (lo_inf != end) mk_bound_axiom(a1, *lo_inf); if (lo_sup != end) mk_bound_axiom(a1, *lo_sup); if (hi_inf != end) mk_bound_axiom(a1, *hi_inf); @@ -1017,8 +1022,8 @@ namespace smt { void theory_arith::mk_bound_axiom(atom* a1, atom* a2) { TRACE("mk_bound_axioms", tout << a1 << " " << a2 << "\n";); theory_var v = a1->get_var(); - literal l1(a1->get_bool_var()); - literal l2(a2->get_bool_var()); + literal l1(a1->get_bool_var()); + literal l2(a2->get_bool_var()); inf_numeral const & k1(a1->get_k()); inf_numeral const & k2(a2->get_k()); atom_kind kind1 = a1->get_atom_kind(); @@ -1027,7 +1032,7 @@ namespace smt { SASSERT(v == a2->get_var()); if (k1 == k2 && kind1 == kind2) return; SASSERT(k1 != k2 || kind1 != kind2); - parameter coeffs[3] = { parameter(symbol("farkas")), + parameter coeffs[3] = { parameter(symbol("farkas")), parameter(rational(1)), parameter(rational(1)) }; if (kind1 == A_LOWER) { @@ -1059,7 +1064,7 @@ namespace smt { } else { // k1 < k2, k2 <= x => ~(x <= k1) - mk_clause(~l1, ~l2, 3, coeffs); + mk_clause(~l1, ~l2, 3, coeffs); if (v_is_int && k1 == k2 - inf_numeral(1)) { // x <= k1 or k1+l <= x mk_clause(l1, l2, 3, coeffs); @@ -1077,7 +1082,7 @@ namespace smt { // k1 <= hi_sup , x <= k1 => x <= hi_sup mk_clause(~l1, l2, 3, coeffs); } - } + } } template @@ -1085,7 +1090,7 @@ namespace smt { CTRACE("arith_verbose", !m_new_atoms.empty(), tout << "flush bound axioms\n";); while (!m_new_atoms.empty()) { - ptr_vector atoms; + ptr_vector atoms; atoms.push_back(m_new_atoms.back()); m_new_atoms.pop_back(); theory_var v = atoms.back()->get_var(); @@ -1096,8 +1101,8 @@ namespace smt { m_new_atoms.pop_back(); --i; } - } - CTRACE("arith", atoms.size() > 1, + } + CTRACE("arith", atoms.size() > 1, for (unsigned i = 0; i < atoms.size(); ++i) { atoms[i]->display(*this, tout); tout << "\n"; }); @@ -1124,10 +1129,10 @@ namespace smt { hi_inf1 = next_inf(a1, A_UPPER, hi_inf, end, fhi_inf); lo_sup1 = next_sup(a1, A_LOWER, lo_sup, end, flo_sup); hi_sup1 = next_sup(a1, A_UPPER, hi_sup, end, fhi_sup); - if (lo_inf1 != end) lo_inf = lo_inf1; - if (lo_sup1 != end) lo_sup = lo_sup1; - if (hi_inf1 != end) hi_inf = hi_inf1; - if (hi_sup1 != end) hi_sup = hi_sup1; + if (lo_inf1 != end) lo_inf = lo_inf1; + if (lo_sup1 != end) lo_sup = lo_sup1; + if (hi_inf1 != end) hi_inf = hi_inf1; + if (hi_sup1 != end) hi_sup = hi_sup1; if (!flo_inf) lo_inf = end; if (!fhi_inf) hi_inf = end; if (!flo_sup) lo_sup = end; @@ -1137,15 +1142,15 @@ namespace smt { if (lo_sup1 != end && lo_sup != end && !visited.contains(*lo_sup)) mk_bound_axiom(a1, *lo_sup); if (hi_inf1 != end && hi_inf != end && !visited.contains(*hi_inf)) mk_bound_axiom(a1, *hi_inf); if (hi_sup1 != end && hi_sup != end && !visited.contains(*hi_sup)) mk_bound_axiom(a1, *hi_sup); - } + } } } template - typename theory_arith::atoms::iterator + typename theory_arith::atoms::iterator theory_arith::first( - atom_kind kind, - typename atoms::iterator it, + atom_kind kind, + typename atoms::iterator it, typename atoms::iterator end) { for (; it != end; ++it) { atom* a = *it; @@ -1155,18 +1160,18 @@ namespace smt { } template - typename theory_arith::atoms::iterator + typename theory_arith::atoms::iterator theory_arith::next_inf( - atom* a1, - atom_kind kind, - typename atoms::iterator it, + atom* a1, + atom_kind kind, + typename atoms::iterator it, typename atoms::iterator end, bool& found_compatible) { inf_numeral const & k1(a1->get_k()); typename atoms::iterator result = end; found_compatible = false; for (; it != end; ++it) { - atom * a2 = *it; + atom * a2 = *it; if (a1 == a2) continue; if (a2->get_atom_kind() != kind) continue; inf_numeral const & k2(a2->get_k()); @@ -1182,17 +1187,17 @@ namespace smt { } template - typename theory_arith::atoms::iterator + typename theory_arith::atoms::iterator theory_arith::next_sup( - atom* a1, - atom_kind kind, - typename atoms::iterator it, + atom* a1, + atom_kind kind, + typename atoms::iterator it, typename atoms::iterator end, bool& found_compatible) { inf_numeral const & k1(a1->get_k()); found_compatible = false; for (; it != end; ++it) { - atom * a2 = *it; + atom * a2 = *it; if (a1 == a2) continue; if (a2->get_atom_kind() != kind) continue; inf_numeral const & k2(a2->get_k()); @@ -1234,7 +1239,7 @@ namespace smt { app * rhs = to_app(n->get_arg(1)); expr * rhs2; if (m_util.is_to_real(rhs, rhs2) && is_app(rhs2)) { rhs = to_app(rhs2); } - if (!m_util.is_numeral(rhs)) { + if (!m_util.is_numeral(rhs)) { UNREACHABLE(); throw default_exception("malformed atomic constraint"); } @@ -1290,7 +1295,7 @@ namespace smt { enode * n1 = ctx.get_enode(lhs); enode * n2 = ctx.get_enode(rhs); // The expression atom may be a theory axiom. In this case, it may not be in simplified form. - // So, an atom such as (= a a) may occur. The procedure mk_axioms, expects n1 != n2. + // So, an atom such as (= a a) may occur. The procedure mk_axioms, expects n1 != n2. // So, we should check it. It doesn't make sense to create an axiom for (= a a) in the arith_eq_adapter. if (n1->get_th_var(get_id()) != null_theory_var && n2->get_th_var(get_id()) != null_theory_var && @@ -1305,7 +1310,7 @@ namespace smt { void theory_arith::apply_sort_cnstr(enode * n, sort * s) { // do nothing... } - + template void theory_arith::assign_eh(bool_var v, bool is_true) { TRACE("arith_verbose", tout << "p" << v << " := " << (is_true?"true":"false") << "\n";); @@ -1320,13 +1325,13 @@ namespace smt { template void theory_arith::relevant_eh(app * n) { TRACE("arith_relevant_eh", tout << "relevant_eh: " << mk_pp(n, get_manager()) << "\n";); - if (m_util.is_mod(n)) + if (m_util.is_mod(n)) mk_idiv_mod_axioms(n->get_arg(0), n->get_arg(1)); else if (m_util.is_rem(n)) mk_rem_axiom(n->get_arg(0), n->get_arg(1)); - else if (m_util.is_div(n)) + else if (m_util.is_div(n)) mk_div_axiom(n->get_arg(0), n->get_arg(1)); - else if (m_util.is_to_int(n)) + else if (m_util.is_to_int(n)) mk_to_int_axiom(n); else if (m_util.is_is_int(n)) mk_is_int_axiom(n); @@ -1335,16 +1340,16 @@ namespace smt { template void theory_arith::new_eq_eh(theory_var v1, theory_var v2) { TRACE("arith_new_eq_eh", tout << "#" << get_enode(v1)->get_owner_id() << " = #" << get_enode(v2)->get_owner_id() << "\n";); - TRACE("arith_new_eq_eh_detail", tout << mk_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << + TRACE("arith_new_eq_eh_detail", tout << mk_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << mk_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); enode * n1 = get_enode(v1); - - if (!m_util.is_int(n1->get_owner()) && + + if (!m_util.is_int(n1->get_owner()) && !m_util.is_real(n1->get_owner())) { return; } - if (m_params.m_arith_eq_bounds) { + if (m_params.m_arith_eq_bounds) { enode * n2 = get_enode(v2); SASSERT(n1->get_root() == n2->get_root()); if (m_util.is_numeral(n1->get_owner())) { @@ -1391,7 +1396,7 @@ namespace smt { template void theory_arith::new_diseq_eh(theory_var v1, theory_var v2) { - TRACE("arith_new_diseq_eh", tout << mk_bounded_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << + TRACE("arith_new_diseq_eh", tout << mk_bounded_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << mk_bounded_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); m_stats.m_assert_diseq++; m_arith_eq_adapter.new_diseq_eh(v1, v2); @@ -1456,8 +1461,8 @@ namespace smt { result = FC_GIVEUP; break; case FC_CONTINUE: - TRACE("arith", - tout << "continue arith..." + TRACE("arith", + tout << "continue arith..." << (get_context().inconsistent()?"inconsistent\n":"\n");); return FC_CONTINUE; } @@ -1475,8 +1480,8 @@ namespace smt { TRACE("arith_eq_adapter_info", m_arith_eq_adapter.display_already_processed(tout);); TRACE("arith", display(tout);); - if (!propagate_core()) - return FC_CONTINUE; + if (!propagate_core()) + return FC_CONTINUE; if (delayed_assume_eqs()) return FC_CONTINUE; get_context().push_trail(value_trail(m_final_check_idx)); @@ -1493,12 +1498,12 @@ namespace smt { TRACE("arith", tout << "result: " << result << "\n";); return result; } - + template bool theory_arith::can_propagate() { return process_atoms() && m_asserted_qhead < m_asserted_bounds.size(); } - + template void theory_arith::propagate() { TRACE("arith_propagate", tout << "propagate\n"; display(tout);); @@ -1512,7 +1517,7 @@ namespace smt { CASSERT("arith", wf_rows()); CASSERT("arith", wf_columns()); CASSERT("arith", valid_row_assignment()); - + flush_bound_axioms(); propagate_linear_monomials(); while (m_asserted_qhead < m_asserted_bounds.size()) { @@ -1520,7 +1525,7 @@ namespace smt { m_asserted_qhead++; if (!assert_bound(b)) { failed(); - return false; + return false; } } if (!make_feasible()) { @@ -1545,15 +1550,15 @@ namespace smt { CASSERT("arith", satisfy_bounds()); return true; } - + template void theory_arith::failed() { restore_assignment(); m_to_patch.reset(); m_to_check.reset(); m_in_to_check.reset(); - } - + } + template void theory_arith::flush_eh() { std::for_each(m_atoms.begin(), m_atoms.end(), delete_proc()); @@ -1561,7 +1566,7 @@ namespace smt { std::for_each(m_bounds_to_delete.begin(), m_bounds_to_delete.end(), delete_proc()); m_bounds_to_delete.reset(); } - + template void theory_arith::reset_eh() { m_stats.reset(); @@ -1666,7 +1671,7 @@ namespace smt { result.neg(); return is_diff; } - + template theory_arith::theory_arith(ast_manager & m, theory_arith_params & params): theory(m.mk_family_id("arith")), @@ -1702,8 +1707,8 @@ namespace smt { } template - theory* theory_arith::mk_fresh(context* new_ctx) { - return alloc(theory_arith, new_ctx->get_manager(), m_params); + theory* theory_arith::mk_fresh(context* new_ctx) { + return alloc(theory_arith, new_ctx->get_manager(), m_params); } template @@ -1717,7 +1722,7 @@ namespace smt { // Add Row // // ----------------------------------- - + /** \brief Set: row1 <- row1 + coeff * row2 */ @@ -1735,8 +1740,8 @@ namespace smt { CASSERT("arith", check_null_var_pos()); r1.save_var_pos(m_var_pos); - - // + + // // loop over variables in row2, // add terms in row2 to row1. // @@ -1785,7 +1790,7 @@ namespace smt { r_entry.m_coeff -= it->m_coeff); } else { - ADD_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff *= coeff, + ADD_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff *= coeff, r_entry.m_coeff += it->m_coeff * coeff); } @@ -1797,17 +1802,17 @@ namespace smt { theory_var v = r1.get_base_var(); if (is_int(v) && !get_value(v).is_int()) gcd_test(r1); - } + } } /** \brief Set r1 <- r1 + a_xs[0].m_coeff * get_var_row(a_xs[0].m_var) + ... + a_xs[0].m_coeff * get_var_row(a_xs[sz-1].m_var) - + \pre For all i in [0..sz-1]. not is_non_base(a_xs[i]) */ template void theory_arith::add_rows(unsigned r1, unsigned sz, linear_monomial * a_xs) { - if (sz == 0) + if (sz == 0) return; for (unsigned i = 0; i < sz; i++) { linear_monomial & m = a_xs[i]; @@ -1837,7 +1842,7 @@ namespace smt { } m_changed_assignment = true; } - + template void theory_arith::discard_update_trail() { m_in_update_trail_stack.reset(); @@ -1876,15 +1881,15 @@ namespace smt { } /** - \brief m_value[v] += delta, and update dependent (non-base) variables. + \brief m_value[v] += delta, and update dependent (non-base) variables. */ template void theory_arith::update_value(theory_var v, inf_numeral const & delta) { update_value_core(v, delta); - + column & c = m_columns[v]; c.compress_if_needed(m_rows); - + inf_numeral delta2; typename svector::const_iterator it = c.begin_entries(); typename svector::const_iterator end = c.end_entries(); @@ -1929,7 +1934,7 @@ namespace smt { int r_id = get_var_row(x_i); row & r = m_rows[r_id]; - + SASSERT(r.is_coeff_of(x_j, a_ij)); #define DIVIDE_ROW(_ADJUST_COEFF_) \ @@ -1953,14 +1958,14 @@ namespace smt { set_var_row(x_i, -1); set_var_row(x_j, r_id); - + SASSERT(r.m_base_var == x_i); r.m_base_var = x_j; set_var_kind(x_i, NON_BASE); set_var_kind(x_j, BASE); - - eliminate(x_j, apply_gcd_test); + + eliminate(x_j, apply_gcd_test); CASSERT("arith", wf_rows()); CASSERT("arith", wf_columns()); @@ -1979,7 +1984,7 @@ namespace smt { /** \brief Eliminate x_i from the rows different from get_var_row(x_i) - + If Lazy = true, then x_i is only eliminated from base rows. */ template @@ -1998,7 +2003,7 @@ namespace smt { unsigned r1_sz = m_rows[r_id].size(); if (it->m_row_id != static_cast(r_id)) { row & r2 = m_rows[it->m_row_id]; - theory_var s2 = r2.m_base_var; + theory_var s2 = r2.m_base_var; if (s2 != null_theory_var && (!Lazy || is_base(s2))) { a_kj = r2[it->m_row_idx].m_coeff; a_kj.neg(); @@ -2006,12 +2011,12 @@ namespace smt { get_manager().limit().inc((r1_sz + r2.size()) * (a_kj.storage_size())); } } - else { + else { s_pos = i; } } - } - CTRACE("eliminate", !Lazy && c.size() != 1, + } + CTRACE("eliminate", !Lazy && c.size() != 1, tout << "eliminating v" << x_i << ", Lazy: " << Lazy << ", c.size: " << c.size() << "\n"; display(tout);); SASSERT(Lazy || c.size() == 1); @@ -2051,7 +2056,7 @@ namespace smt { pivot(x_i, x_j, a_ij, m_eager_gcd); CASSERT("arith", valid_row_assignment()); } - + /** \brief Return the number of base variables that are non free and are v dependent. The function adds 1 to the result if v is non free. @@ -2077,9 +2082,9 @@ namespace smt { } return result; } - + /** - \brief Using Bland's rule, select a variable x_j in the row r defining the base var x_i, + \brief Using Bland's rule, select a variable x_j in the row r defining the base var x_i, s.t. x_j can be used to patch the error in x_i. Return null_theory_var if there is no x_j. Otherwise, return x_j and store its coefficient in out_a_ij. @@ -2090,29 +2095,29 @@ namespace smt { theory_var max = get_num_vars(); theory_var result = max; row const & r = m_rows[get_var_row(x_i)]; - + typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); - for (; it != end; ++it) { - if (!it->is_dead()) { - theory_var x_j = it->m_var; - numeral const & a_ij = it->m_coeff; + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var x_j = it->m_var; + numeral const & a_ij = it->m_coeff; bool is_neg = is_below ? a_ij.is_neg() : a_ij.is_pos(); - bool is_pos = !is_neg; + bool is_pos = !is_neg; if (x_i != x_j && ((is_pos && above_lower(x_j)) || (is_neg && below_upper(x_j)))) { SASSERT(is_non_base(x_j)); - if (x_j < result) { - result = x_j; - out_a_ij = a_ij; + if (x_j < result) { + result = x_j; + out_a_ij = a_ij; } } } } return result < max ? result : null_theory_var; } - + /** - \brief Select a variable x_j in the row r defining the base var x_i, + \brief Select a variable x_j in the row r defining the base var x_i, s.t. x_j can be used to patch the error in x_i. Return null_theory_var if there is no x_j. Otherwise, return x_j and store its coefficient in out_a_ij. @@ -2135,13 +2140,13 @@ namespace smt { for (; it != end; ++it) { - if (!it->is_dead()) { - theory_var x_j = it->m_var; - numeral const & a_ij = it->m_coeff; - + if (!it->is_dead()) { + theory_var x_j = it->m_var; + numeral const & a_ij = it->m_coeff; + bool is_neg = is_below ? a_ij.is_neg() : a_ij.is_pos(); - bool is_pos = !is_neg; - if (x_i != x_j && ((is_pos && above_lower(x_j)) || (is_neg && below_upper(x_j)))) { + bool is_pos = !is_neg; + if (x_i != x_j && ((is_pos && above_lower(x_j)) || (is_neg && below_upper(x_j)))) { int num = get_num_non_free_dep_vars(x_j, best_so_far); int col_sz = m_columns[x_j].size(); if (num < best_so_far || (num == best_so_far && col_sz < best_col_sz)) { @@ -2157,13 +2162,13 @@ namespace smt { result = x_j; out_a_ij = a_ij; } - } + } } } } return result < max ? result : null_theory_var; } - + /** \brief Wrapper for select_blands_pivot_core and select_pivot_core */ @@ -2184,7 +2189,7 @@ namespace smt { // Make feasible // // ----------------------------------- - + /** \brief Make the given variable feasible. This method assumes that x_i is a base var. Return false if it was not possible to @@ -2192,8 +2197,8 @@ namespace smt { */ template bool theory_arith::make_var_feasible(theory_var x_i) { - CTRACE("arith_bug", !is_base(x_i), - tout << "x_i: " << x_i << ", below_lower(x_i): " << below_lower(x_i) << + CTRACE("arith_bug", !is_base(x_i), + tout << "x_i: " << x_i << ", below_lower(x_i): " << below_lower(x_i) << ", above_upper(x_i): " << above_upper(x_i) << "\n"; display(tout);); SASSERT(is_base(x_i)); @@ -2245,7 +2250,7 @@ namespace smt { continue; SASSERT(curr_error > inf_numeral(0)); if (best == null_theory_var || (!least && curr_error > best_error) || (least && curr_error < best_error)) { - TRACE("select_pivot", tout << "best: " << best << " v" << v + TRACE("select_pivot", tout << "best: " << best << " v" << v << ", best_error: " << best_error << ", curr_error: " << curr_error << "\n";); best = v; best_error = curr_error; @@ -2266,7 +2271,7 @@ namespace smt { m_to_patch.erase(best); return best; } - + template theory_var theory_arith::select_smallest_var() { return m_to_patch.erase_min(); @@ -2277,7 +2282,7 @@ namespace smt { if (m_blands_rule) return select_smallest_var(); switch (m_params.m_arith_pivot_strategy) { - case ARITH_PIVOT_GREATEST_ERROR: + case ARITH_PIVOT_GREATEST_ERROR: return select_greatest_error_var(); case ARITH_PIVOT_LEAST_ERROR: return select_least_error_var(); @@ -2319,12 +2324,12 @@ namespace smt { m_left_basis.insert(v); } } - if (!make_var_feasible(v)) { + if (!make_var_feasible(v)) { TRACE("arith_make_feasible", tout << "make_feasible: unsat\n"; display(tout);); return false; } TRACE("arith_make_feasible_detail", display(tout);); - if (get_context().get_cancel_flag()) { + if (get_context().get_cancel_flag()) { return true; } } @@ -2339,7 +2344,7 @@ namespace smt { /** \brief A row is in a sign inconsistency when it is implying a lower (upper) bound on x_i, which is above (below) its known - upper (lower) bound. + upper (lower) bound. */ template void theory_arith::sign_row_conflict(theory_var x_i, bool is_below) { @@ -2353,7 +2358,7 @@ namespace smt { // if x_i is an integer variable, then delta can be negative: // // Example: x_i <= 0 get_value(x_i) = 1/4 - // + // // The value is above the upper bound. // Since x_i is an integer, get_epsilon(x_i) = 1, and delta = -3/4 @@ -2383,7 +2388,7 @@ namespace smt { antecedents ante(*this); explain_bound(r, idx, !is_below, delta, ante); b->push_justification(ante, numeral(1), coeffs_enabled()); - + TRACE("sign_row_conflict", tout << "v" << x_i << " is_below: " << is_below << " delta: " << delta << "\n"; display_var(tout, x_i); tout << "is_below_lower: " << below_lower(x_i) << ", is_above_upper: " << above_upper(x_i) << "\n"; ante.display(tout);); @@ -2397,7 +2402,7 @@ namespace smt { // Assert bound // // ----------------------------------- - + /** \brief Assert x >= k, return false if a conflict is detected. */ @@ -2409,7 +2414,7 @@ namespace smt { bound * u = upper(v); bound * l = lower(v); - + if (u && k > u->get_value()) { sign_bound_conflict(u, b); return false; @@ -2419,7 +2424,7 @@ namespace smt { // redundant return true; } - + switch (get_var_kind(v)) { case QUASI_BASE: quasi_base_row2base_row(get_var_row(v)); @@ -2438,10 +2443,10 @@ namespace smt { push_bound_trail(v, l, false); set_bound(b, false); - + if (propagation_mode() != BP_NONE) mark_rows_for_bound_prop(v); - + return true; } @@ -2457,12 +2462,12 @@ namespace smt { TRACE("arith", display_bound(tout, b); tout << "v" << v << " <= " << k << "\n";); bound * u = upper(v); bound * l = lower(v); - + if (l && k < l->get_value()) { sign_bound_conflict(l, b); return false; } - + if (u && k >= u->get_value()) { // redundant return true; @@ -2474,7 +2479,7 @@ namespace smt { SASSERT(get_var_kind(v) == BASE); case BASE: if (!m_to_patch.contains(v) && get_value(v) > k) { - TRACE("to_patch_bug", tout << "need to be patched (assert upper): "; display_var(tout, v);); + TRACE("to_patch_bug", tout << "need to be patched (assert upper): "; display_var(tout, v);); m_to_patch.insert(v); } break; @@ -2486,12 +2491,12 @@ namespace smt { push_bound_trail(v, u, true); set_bound(b, true); - + if (propagation_mode() != BP_NONE) mark_rows_for_bound_prop(v); return true; - } + } template bool theory_arith::assert_bound(bound * b) { @@ -2507,7 +2512,7 @@ namespace smt { bool result = true; switch (b->get_bound_kind()) { - case B_LOWER: + case B_LOWER: m_stats.m_assert_lower++; result = assert_lower(b); break; @@ -2516,7 +2521,7 @@ namespace smt { result = assert_upper(b); break; } - + TRACE("arith_bound", tout << "result: " << result << "\n"; display(tout);); return result; } @@ -2541,7 +2546,7 @@ namespace smt { // Bound propagation // // ----------------------------------- - + /** \brief Mark the row r1 for bound propagation. */ @@ -2554,7 +2559,7 @@ namespace smt { } /** - \brief Mark all rows that contain v for bound propagation. + \brief Mark all rows that contain v for bound propagation. */ template void theory_arith::mark_rows_for_bound_prop(theory_var v) { @@ -2600,7 +2605,7 @@ namespace smt { - lower_idx >= 0 : row can imply a lower bound for the monomial at 'lower_idx' - lower_idx == -1 : row can imply a lower bound for every monomial in the row. - lower_idx == -2 : row cannot be used to imply a lower bound. - + - upper_idx >= 0 : row can imply a upper bound for the monomial at 'upper_idx' - upper_idx == -1 : row can imply a upper bound for every monomial in the row. - upper_idx == -2 : row cannot be used to imply a upper bound. @@ -2608,7 +2613,7 @@ namespace smt { template void theory_arith::is_row_useful_for_bound_prop(row const & r, int & lower_idx, int & upper_idx) const { lower_idx = -1; - upper_idx = -1; + upper_idx = -1; typename vector::const_iterator it = r.begin_entries(); typename vector::const_iterator end = r.end_entries(); for (int i = 0; it != end; ++it, ++i) { @@ -2667,7 +2672,7 @@ namespace smt { // implied_k is a lower bound for entry.m_var bound * curr = lower(entry.m_var); if (curr == nullptr || implied_k > curr->get_value()) { - TRACE("arith_imply_bound", + TRACE("arith_imply_bound", tout << "implying lower bound for v" << entry.m_var << " " << implied_k << " using row:\n"; display_row_info(tout, r); display_var(tout, entry.m_var);); @@ -2675,21 +2680,21 @@ namespace smt { } } else { - // implied_k is an upper bound for it->m_var + // implied_k is an upper bound for it->m_var bound * curr = upper(entry.m_var); if (curr == nullptr || implied_k < curr->get_value()) { - TRACE("arith_imply_bound", + TRACE("arith_imply_bound", tout << "implying upper bound for v" << entry.m_var << " " << implied_k << " using row:\n"; display_row_info(tout, r); display_var(tout, entry.m_var);); mk_implied_bound(r, idx, is_lower, entry.m_var, B_UPPER, implied_k); } } - } + } } /** - \brief Auxiliary method. See is_row_useful_for_bound_prop + \brief Auxiliary method. See is_row_useful_for_bound_prop If is_lower = true (false), then imply a lower (upper) bound for all monomials in the row. The monomial bounds are used to compute bounds @@ -2697,7 +2702,7 @@ namespace smt { */ template void theory_arith::imply_bound_for_all_monomials(row const & r, bool is_lower) { - // Traverse the row once and compute + // Traverse the row once and compute // bb = (Sum_{a_i < 0} -a_i*lower(x_i)) + (Sum_{a_j > 0} -a_j * upper(x_j)) If is_lower = true // bb = (Sum_{a_i > 0} -a_i*lower(x_i)) + (Sum_{a_j < 0} -a_j * upper(x_j)) If is_lower = false inf_numeral bb; @@ -2710,7 +2715,7 @@ namespace smt { bb.submul(it->m_coeff, b); } } - + inf_numeral implied_k; it = r.begin_entries(); for (int idx = 0; it != end; ++it, ++idx) { @@ -2721,7 +2726,7 @@ namespace smt { implied_k.addmul(it->m_coeff, b); // implied_k is a bound for the monomial in position it implied_k /= it->m_coeff; - TRACE("arith_imply_bound", + TRACE("arith_imply_bound", display_var(tout, it->m_var); tout << "implied bound: " << (it->m_coeff.is_pos() ? ">=" : "<=") << implied_k << "\n";); if (it->m_coeff.is_pos() == is_lower) { @@ -2737,7 +2742,7 @@ namespace smt { } } else { - // implied_k is an upper bound for it->m_var + // implied_k is an upper bound for it->m_var bound * curr = upper(it->m_var); if (curr == nullptr || implied_k < curr->get_value()) { // improved upper bound @@ -2754,13 +2759,13 @@ namespace smt { /** \brief Create an explanation for the lower/upper bound of the variable at position idx. - + \remark delta is used for relaxing the explanation. That is, the implied bound can be delta weaker the the computed value. - \remark the is_lower parameter is a little bit counterintuitive. It says if the other monomials + \remark the is_lower parameter is a little bit counterintuitive. It says if the other monomials imply a lower (upper) bound for the monomial at position idx. - + Store the result in 'antecedent' */ template @@ -2770,7 +2775,7 @@ namespace smt { return; context & ctx = get_context(); row_entry const & entry = r[idx]; - numeral coeff = entry.m_coeff; + numeral coeff = entry.m_coeff; if (relax_bounds()) { // if the variable v at position idx can have a delta increase (decrease) of 'delta', then // the monomial (coeff * v) at position idx can have a delta increase (decrease) of '|coeff| * delta' @@ -2811,7 +2816,7 @@ namespace smt { // limit_k1 += delta * coeff; limit_k1.addmul(inv_coeff, delta); } - TRACE("propagate_bounds_bug", tout << "is_b_lower: " << is_b_lower << " k1: " << k_1 << " limit_k1: " + TRACE("propagate_bounds_bug", tout << "is_b_lower: " << is_b_lower << " k1: " << k_1 << " limit_k1: " << limit_k1 << " delta: " << delta << " coeff: " << coeff << "\n";); inf_numeral k_2 = k_1; atom * new_atom = nullptr; @@ -2891,7 +2896,7 @@ namespace smt { delta = k; delta -= k2; } - TRACE("propagate_bounds", tout << "v" << v << " >= " << k << ", v" << v << " >= " << k2 << ", delta: " << delta << "\n"; + TRACE("propagate_bounds", tout << "v" << v << " >= " << k << ", v" << v << " >= " << k2 << ", delta: " << delta << "\n"; display_row(tout, r);); assign_bound_literal(l, r, idx, is_lower, delta); } @@ -2901,13 +2906,13 @@ namespace smt { // example: // k = -1/5*epsilon // k2 = 0 - // Thus, v <= -1/5*epsilon + // Thus, v <= -1/5*epsilon // (not v >= 0) which is equivalent to v <= -epsilon. delta = k2; delta -= k; delta -= epsilon; if (delta.is_nonneg()) { - TRACE("propagate_bounds", tout << "v" << v << " <= " << k << ", not v" << v << " >= " << k2 << ", delta: " << delta << "\n"; + TRACE("propagate_bounds", tout << "v" << v << " <= " << k << ", not v" << v << " >= " << k2 << ", delta: " << delta << "\n"; display_row(tout, r);); assign_bound_literal(~l, r, idx, is_lower, delta); } @@ -2922,18 +2927,18 @@ namespace smt { delta -= k2; delta -= epsilon; if (delta.is_nonneg()) { - TRACE("propagate_bounds", tout << "v" << v << " >= " << k << ", not v" << v << " <= " << k2 << ", delta: " << delta << "\n"; + TRACE("propagate_bounds", tout << "v" << v << " >= " << k << ", not v" << v << " <= " << k2 << ", delta: " << delta << "\n"; display_row(tout, r);); assign_bound_literal(~l, r, idx, is_lower, delta); } } - // v <= k k <= k2 |- v <= k2 + // v <= k k <= k2 |- v <= k2 if (kind == B_UPPER && k <= k2) { if (relax_bounds()) { delta = k2; delta -= k; } - TRACE("propagate_bounds", tout << "v" << v << " <= " << k << ", v" << v << " <= " << k2 << ", delta: " << delta << "\n"; + TRACE("propagate_bounds", tout << "v" << v << " <= " << k << ", v" << v << " <= " << k2 << ", delta: " << delta << "\n"; display_row(tout, r);); assign_bound_literal(l, r, idx, is_lower, delta); } @@ -2947,7 +2952,7 @@ namespace smt { context & ctx = get_context(); if (dump_lemmas()) { TRACE("arith", ante.display(tout) << " --> "; ctx.display_detailed_literal(tout, l); tout << "\n";); - ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().c_ptr(), + ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().c_ptr(), ante.eqs().size(), ante.eqs().c_ptr(), l); } } @@ -2956,7 +2961,7 @@ namespace smt { void theory_arith::dump_lemmas(literal l, derived_bound const& ante) { context & ctx = get_context(); if (dump_lemmas()) { - ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().c_ptr(), + ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().c_ptr(), ante.eqs().size(), ante.eqs().c_ptr(), l); } } @@ -2968,10 +2973,10 @@ namespace smt { antecedents ante(*this); explain_bound(r, idx, is_lower, delta, ante); dump_lemmas(l, ante); - - TRACE("propagate_bounds", + + TRACE("propagate_bounds", ante.display(tout) << " --> "; - ctx.display_detailed_literal(tout, l); + ctx.display_detailed_literal(tout, l); tout << "\n";); if (ante.lits().size() < small_lemma_size() && ante.eqs().empty()) { literal_vector & lits = m_tmp_literal_vector2; @@ -2992,8 +2997,8 @@ namespace smt { region & r = ctx.get_region(); ctx.assign(l, ctx.mk_justification( ext_theory_propagation_justification( - get_id(), r, ante.lits().size(), ante.lits().c_ptr(), - ante.eqs().size(), ante.eqs().c_ptr(), l, + get_id(), r, ante.lits().size(), ante.lits().c_ptr(), + ante.eqs().size(), ante.eqs().c_ptr(), l, ante.num_params(), ante.params("assign-bounds")))); } } @@ -3014,29 +3019,29 @@ namespace smt { int lower_idx; int upper_idx; is_row_useful_for_bound_prop(r, lower_idx, upper_idx); - + if (lower_idx >= 0) { imply_bound_for_monomial(r, lower_idx, true); } else if (lower_idx == -1) { imply_bound_for_all_monomials(r, true); } - + if (upper_idx >= 0) { imply_bound_for_monomial(r, upper_idx, false); } else if (upper_idx == -1) { imply_bound_for_all_monomials(r, false); } - - // sneaking cheap eq detection in this loop + + // sneaking cheap eq detection in this loop propagate_cheap_eq(*it); } - + #if 0 theory_var v = r.get_base_var(); if (!is_int(v) || get_value(v).is_int()) { - // If an integer value is not assigned to an integer value, then + // If an integer value is not assigned to an integer value, then // bound propagation can diverge. m_in_to_check.remove(v); } @@ -3064,15 +3069,15 @@ namespace smt { dump_lemmas(false_literal, ante); set_conflict(ante.lits().size(), ante.lits().c_ptr(), ante.eqs().size(), ante.eqs().c_ptr(), bounds, proof_rule); } - + template - void theory_arith::set_conflict(unsigned num_literals, literal const * lits, unsigned num_eqs, enode_pair const * eqs, + void theory_arith::set_conflict(unsigned num_literals, literal const * lits, unsigned num_eqs, enode_pair const * eqs, antecedents& bounds, char const* proof_rule) { SASSERT(num_literals != 0 || num_eqs != 0); context & ctx = get_context(); m_stats.m_conflicts++; m_num_conflicts++; - TRACE("arith_conflict", + TRACE("arith_conflict", tout << "scope: " << ctx.get_scope_level() << "\n"; for (unsigned i = 0; i < num_literals; i++) { ctx.display_detailed_literal(tout, lits[i]); @@ -3095,13 +3100,13 @@ namespace smt { record_conflict(num_literals, lits, num_eqs, eqs, bounds.num_params(), bounds.params(proof_rule)); ctx.set_conflict( ctx.mk_justification( - ext_theory_conflict_justification(get_id(), ctx.get_region(), num_literals, lits, num_eqs, eqs, + ext_theory_conflict_justification(get_id(), ctx.get_region(), num_literals, lits, num_eqs, eqs, bounds.num_params(), bounds.params(proof_rule)))); } /** \brief Collect the proofs for the fixed variables in the given row. Store - the proofs in result. + the proofs in result. */ template void theory_arith::collect_fixed_var_justifications(row const & r, antecedents& antecedents) const { @@ -3110,7 +3115,7 @@ namespace smt { for (; it != end; ++it) { if (!it->is_dead() && is_fixed(it->m_var)) { lower(it->m_var)->push_justification(antecedents, it->m_coeff, coeffs_enabled()); - upper(it->m_var)->push_justification(antecedents, it->m_coeff, coeffs_enabled()); + upper(it->m_var)->push_justification(antecedents, it->m_coeff, coeffs_enabled()); } } } @@ -3124,33 +3129,33 @@ namespace smt { // // The arithmetic module uses infinitesimals. So, // an inf_numeral (n,k) represents n + k*epsilon - // where epsilon is a very small number. + // where epsilon is a very small number. // In order to generate a model, we need to compute // a value for epsilon in a way all bounds remain // satisfied. // // 1) Handling inequalities: (n1, k1) <= (n2, k2) - // - // The only intersting case is n1 < n2 and k1 > k2. + // + // The only intersting case is n1 < n2 and k1 > k2. // Using the definition of infinitesimal numbers // we have: // n1 + k1 * epsilon <= n2 + k2 - epsilon // Therefore: // epsilon <= (n2 - n1) / (k1 - k2) - // + // // Starting at Z3 V2.0, we split disequalities. // So, we do not need to handle them. If we decide // to support them again in the future: // // 2) Handling disequalities: (n1, k1) /= n2 - // + // // case a) k1 is positive and n1 < n2 // Thus, epsilon < (n2 - n1) / k1 // => epsilon <= (n2 - n1) / 2*k1 // // case b) k1 is negative and n1 > n2 - // Similarly, epsilon <= (n2 - n1) / 2*k1 - // + // Similarly, epsilon <= (n2 - n1) / 2*k1 + // /** \brief Update the value of epsilon using the inequality l <= u @@ -3166,7 +3171,7 @@ namespace smt { } SASSERT(m_epsilon.is_pos()); } - + template void theory_arith::compute_epsilon() { m_epsilon = numeral(1); @@ -3184,21 +3189,21 @@ namespace smt { /** The epsilon computed by compute_epsilon may accidentally make two shared - variables to have the same assignment. This method keeps dividing + variables to have the same assignment. This method keeps dividing epsilon by 2 until this "clash" does not occur. Here is an example of the problem - + Assignment: x -> 9.5 - y -> 10 - epsilon - + y -> 10 - epsilon + x and y have different assignments. However, if compute_epsilon sets epsilon to 0.5, then x and y become 9.5. However, an equality is not propagated to the core since in the assignment above they are assigned to distinct values. - - This bug was reported by Marcello Bersani. + + This bug was reported by Marcello Bersani. Remark: this is not really a soundness bug. The result sat/unsat produced by Z3 was still correct. - However, the model construction was incorrect. Perhaps, this explains why this bug was not + However, the model construction was incorrect. Perhaps, this explains why this bug was not detected before. */ template @@ -3221,7 +3226,7 @@ namespace smt { if (mapping.find(value, v2)) { SASSERT(!is_int_src(v2)); if (get_value(v) != get_value(v2)) { - // v and v2 are not known to be equal. + // v and v2 are not known to be equal. // The choice of m_epsilon is making them equal. TRACE("refine_epsilon", tout << "v" << v << " v" << v2 << " " << get_value(v) << " " << get_value(v2) << " " << value << std::endl; @@ -3274,7 +3279,7 @@ namespace smt { template bool theory_arith::to_expr(inf_numeral const& val, bool is_int, expr_ref & r) { - if (val.get_infinitesimal().is_zero()) { + if (val.get_infinitesimal().is_zero()) { numeral _val = val.get_rational(); r = m_util.mk_numeral(_val.to_rational(), is_int); return true; @@ -3292,25 +3297,25 @@ namespace smt { } template - bool theory_arith::get_lower(enode * n, expr_ref & r) { + bool theory_arith::get_lower(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); bound* b = (v == null_theory_var) ? nullptr : lower(v); return b && to_expr(b->get_value(), is_int(v), r); } - + template - bool theory_arith::get_upper(enode * n, expr_ref & r) { + bool theory_arith::get_upper(enode * n, expr_ref & r) { theory_var v = n->get_th_var(get_id()); bound* b = (v == null_theory_var) ? nullptr : upper(v); return b && to_expr(b->get_value(), is_int(v), r); } - + // ----------------------------------- // // Backtracking // // ----------------------------------- - + template void theory_arith::push_scope_eh() { theory::push_scope_eh(); @@ -3415,7 +3420,7 @@ namespace smt { --it; m_unassigned_atoms[*it]++; } - + m_unassigned_atoms_trail.shrink(old_trail_size); } @@ -3432,7 +3437,7 @@ namespace smt { SASSERT(m_var_occs[v].back() == a); m_var_occs[v].pop_back(); dealloc(a); - } + } m_atoms.shrink(old_size); } @@ -3444,7 +3449,7 @@ namespace smt { --it; bound * b = *it; dealloc(b); - } + } m_bounds_to_delete.shrink(old_size); } @@ -3461,7 +3466,7 @@ namespace smt { SASSERT(m_columns[v].size() == 1); del_row(get_var_row(v)); TRACE("arith_make_feasible", tout << "del row v" << v << "\n";); - break; + break; case BASE: SASSERT(lazy_pivoting_lvl() != 0 || m_columns[v].size() == 1); if (lazy_pivoting_lvl() > 0) @@ -3519,7 +3524,7 @@ namespace smt { r.reset(); m_dead_rows.push_back(r_id); } - + /** \brief reset and retrieve built-in explanation hints for arithmetic lemmmas. */ @@ -3542,4 +3547,3 @@ namespace smt { }; #endif /* THEORY_ARITH_CORE_H_ */ - diff --git a/src/smt/theory_fpa.h b/src/smt/theory_fpa.h index 8cd01d2e9..75bcec13c 100644 --- a/src/smt/theory_fpa.h +++ b/src/smt/theory_fpa.h @@ -62,7 +62,7 @@ namespace smt { return true; } - expr * get_fresh_value(sort * s) override { NOT_IMPLEMENTED_YET(); } + expr * get_fresh_value(sort * s) override { return get_some_value(s); } void register_value(expr * n) override { /* Ignore */ } app * mk_value(mpf const & x) { diff --git a/src/smt/theory_pb.cpp b/src/smt/theory_pb.cpp index d45590bff..953ecea2c 100644 --- a/src/smt/theory_pb.cpp +++ b/src/smt/theory_pb.cpp @@ -2251,7 +2251,7 @@ namespace smt { for (unsigned i = 2; i < num_lits; ++i) { process_antecedent(cls.get_literal(i), offset); } - TRACE("pb", tout << literal_vector(cls.get_num_literals(), cls.begin_literals()) << "\n";); + TRACE("pb", tout << literal_vector(cls.get_num_literals(), cls.begin()) << "\n";); break; } case b_justification::BIN_CLAUSE: diff --git a/src/smt/theory_seq.cpp b/src/smt/theory_seq.cpp index b704795b2..d5fe54fae 100644 --- a/src/smt/theory_seq.cpp +++ b/src/smt/theory_seq.cpp @@ -712,12 +712,30 @@ unsigned theory_seq::find_branch_start(unsigned k) { return 0; } -bool theory_seq::find_branch_candidate(unsigned& start, dependency* dep, expr_ref_vector const& ls, expr_ref_vector const& rs) { +expr_ref_vector theory_seq::expand_strings(expr_ref_vector const& es) { + expr_ref_vector ls(m); + for (expr* e : es) { + zstring s; + if (m_util.str.is_string(e, s)) { + for (unsigned i = 0; i < s.length(); ++i) { + ls.push_back(m_util.str.mk_unit(m_util.str.mk_char(s, i))); + } + } + else { + ls.push_back(e); + } + } + return ls; +} + +bool theory_seq::find_branch_candidate(unsigned& start, dependency* dep, expr_ref_vector const& _ls, expr_ref_vector const& _rs) { + expr_ref_vector ls = expand_strings(_ls); + expr_ref_vector rs = expand_strings(_rs); if (ls.empty()) { return false; } - expr* l = ls[0]; + expr* l = ls.get(0); if (!is_var(l)) { return false; @@ -735,9 +753,9 @@ bool theory_seq::find_branch_candidate(unsigned& start, dependency* dep, expr_re } for (; start < rs.size(); ++start) { unsigned j = start; - SASSERT(!m_util.str.is_concat(rs[j])); - SASSERT(!m_util.str.is_string(rs[j])); - if (l == rs[j]) { + SASSERT(!m_util.str.is_concat(rs.get(j))); + SASSERT(!m_util.str.is_string(rs.get(j))); + if (l == rs.get(j)) { return false; } if (!can_be_equal(ls.size() - 1, ls.c_ptr() + 1, rs.size() - j - 1, rs.c_ptr() + j + 1)) { @@ -752,8 +770,11 @@ bool theory_seq::find_branch_candidate(unsigned& start, dependency* dep, expr_re } bool all_units = true; - for (unsigned j = 0; all_units && j < rs.size(); ++j) { - all_units &= m_util.str.is_unit(rs[j]); + for (expr* r : rs) { + if (!m_util.str.is_unit(r)) { + all_units = false; + break; + } } if (all_units) { context& ctx = get_context(); @@ -765,20 +786,20 @@ bool theory_seq::find_branch_candidate(unsigned& start, dependency* dep, expr_re lits.push_back(~mk_eq(l, v0, false)); } } - for (unsigned i = 0; i < lits.size(); ++i) { - switch (ctx.get_assignment(lits[i])) { + for (literal lit : lits) { + switch (ctx.get_assignment(lit)) { case l_true: break; case l_false: start = 0; return true; - case l_undef: ctx.force_phase(~lits[i]); start = 0; return true; + case l_undef: ctx.force_phase(~lit); start = 0; return true; } } set_conflict(dep, lits); TRACE("seq", tout << "start: " << start << "\n"; - for (unsigned i = 0; i < lits.size(); ++i) { - ctx.display_literal_verbose(tout << lits[i] << ": ", lits[i]); + for (literal lit : lits) { + ctx.display_literal_verbose(tout << lit << ": ", lit); tout << "\n"; - ctx.display(tout, ctx.get_justification(lits[i].var())); + ctx.display(tout, ctx.get_justification(lit.var())); tout << "\n"; }); return true; @@ -2251,7 +2272,6 @@ bool theory_seq::internalize_term(app* term) { e = ctx.mk_enode(term, false, m.is_bool(term), true); } mk_var(e); - return true; } @@ -2905,6 +2925,9 @@ expr_ref theory_seq::try_expand(expr* e, dependency*& eqs){ } result = ed.first; } + else if (false && m_util.str.is_string(e)) { + result = add_elim_string_axiom(e); + } else { m_expand_todo.push_back(e); } @@ -2927,7 +2950,7 @@ expr_ref theory_seq::expand1(expr* e0, dependency*& eqs) { } else if (m_util.str.is_empty(e) || m_util.str.is_string(e)) { result = e; - } + } else if (m_util.str.is_prefix(e, e1, e2)) { arg1 = try_expand(e1, deps); arg2 = try_expand(e2, deps); @@ -3086,6 +3109,7 @@ void theory_seq::propagate() { void theory_seq::enque_axiom(expr* e) { if (!m_axiom_set.contains(e)) { + TRACE("seq", tout << "add axiom " << mk_pp(e, m) << "\n";); m_axioms.push_back(e); m_axiom_set.insert(e); m_trail_stack.push(push_back_vector(m_axioms)); @@ -3283,20 +3307,21 @@ void theory_seq::add_replace_axiom(expr* r) { tightest_prefix(s, x); } -void theory_seq::add_elim_string_axiom(expr* n) { +expr_ref theory_seq::add_elim_string_axiom(expr* n) { zstring s; + TRACE("seq", tout << mk_pp(n, m) << "\n";); VERIFY(m_util.str.is_string(n, s)); if (s.length() == 0) { - return; + return expr_ref(n, m); } expr_ref result(m_util.str.mk_unit(m_util.str.mk_char(s, s.length()-1)), m); - for (unsigned i = s.length()-1; i > 0; ) { - --i; + for (unsigned i = s.length()-1; i-- > 0; ) { result = mk_concat(m_util.str.mk_unit(m_util.str.mk_char(s, i)), result); } add_axiom(mk_eq(n, result, false)); m_rep.update(n, result, nullptr); m_new_solution = true; + return result; } diff --git a/src/smt/theory_seq.h b/src/smt/theory_seq.h index b6b553dec..2f7cfd2b3 100644 --- a/src/smt/theory_seq.h +++ b/src/smt/theory_seq.h @@ -447,6 +447,7 @@ namespace smt { void insert_branch_start(unsigned k, unsigned s); unsigned find_branch_start(unsigned k); bool find_branch_candidate(unsigned& start, dependency* dep, expr_ref_vector const& ls, expr_ref_vector const& rs); + expr_ref_vector expand_strings(expr_ref_vector const& es); bool can_be_equal(unsigned szl, expr* const* ls, unsigned szr, expr* const* rs) const; lbool assume_equality(expr* l, expr* r); @@ -504,7 +505,7 @@ namespace smt { void add_int_string(expr* e); bool check_int_string(); - void add_elim_string_axiom(expr* n); + expr_ref add_elim_string_axiom(expr* n); void add_at_axiom(expr* n); void add_in_re_axiom(expr* n); void add_itos_axiom(expr* n); diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index 0ef008927..f2432b4fb 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -8388,13 +8388,12 @@ namespace smt { lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); get_context().internalize(target_term, false); + enode* e1 = get_context().get_enode(target_term); for (unsigned i = 0; i < unsat_core.size(); ++i) { app * core_term = to_app(unsat_core.get(i)); // not sure if this is the correct way to compare terms in this context - enode * e1; - enode * e2; - e1 = get_context().get_enode(target_term); - e2 = get_context().get_enode(core_term); + if (!get_context().e_internalized(core_term)) continue; + enode *e2 = get_context().get_enode(core_term); if (e1 == e2) { TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); return l_undef; diff --git a/src/solver/check_sat_result.cpp b/src/solver/check_sat_result.cpp index 0d3c112b3..28e6afeca 100644 --- a/src/solver/check_sat_result.cpp +++ b/src/solver/check_sat_result.cpp @@ -48,9 +48,11 @@ void simple_check_sat_result::collect_statistics(statistics & st) const { st.copy(m_stats); } -void simple_check_sat_result::get_unsat_core(ptr_vector & r) { - if (m_status == l_false) +void simple_check_sat_result::get_unsat_core(expr_ref_vector & r) { + if (m_status == l_false) { + r.reset(); r.append(m_core.size(), m_core.c_ptr()); + } } void simple_check_sat_result::get_model_core(model_ref & m) { diff --git a/src/solver/check_sat_result.h b/src/solver/check_sat_result.h index 716c68b00..7e698b806 100644 --- a/src/solver/check_sat_result.h +++ b/src/solver/check_sat_result.h @@ -47,15 +47,10 @@ public: virtual ~check_sat_result() {} void inc_ref() { m_ref_count++; } void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) dealloc(this); } - void set_status(lbool r) { m_status = r; } + lbool set_status(lbool r) { return m_status = r; } lbool status() const { return m_status; } virtual void collect_statistics(statistics & st) const = 0; - virtual void get_unsat_core(ptr_vector & r) = 0; - virtual void get_unsat_core(expr_ref_vector & r) { - ptr_vector core; - get_unsat_core(core); - r.append(core.size(), core.c_ptr()); - } + virtual void get_unsat_core(expr_ref_vector & r) = 0; void set_model_converter(model_converter* mc) { m_mc0 = mc; } model_converter* mc0() const { return m_mc0.get(); } virtual void get_model_core(model_ref & m) = 0; @@ -87,7 +82,7 @@ struct simple_check_sat_result : public check_sat_result { ~simple_check_sat_result() override; ast_manager& get_manager() const override { return m_proof.get_manager(); } void collect_statistics(statistics & st) const override; - void get_unsat_core(ptr_vector & r) override; + void get_unsat_core(expr_ref_vector & r) override; void get_model_core(model_ref & m) override; proof * get_proof() override; std::string reason_unknown() const override; diff --git a/src/solver/combined_solver.cpp b/src/solver/combined_solver.cpp index a47302f5d..ad166d425 100644 --- a/src/solver/combined_solver.cpp +++ b/src/solver/combined_solver.cpp @@ -298,7 +298,7 @@ public: m_solver1->collect_statistics(st); } - void get_unsat_core(ptr_vector & r) override { + void get_unsat_core(expr_ref_vector & r) override { if (m_use_solver1_results) m_solver1->get_unsat_core(r); else diff --git a/src/solver/mus.cpp b/src/solver/mus.cpp index e4ebd7e3b..094b27ed3 100644 --- a/src/solver/mus.cpp +++ b/src/solver/mus.cpp @@ -50,12 +50,12 @@ struct mus::imp { } bool is_literal(expr* lit) const { - expr* l; + expr* l; return is_uninterp_const(lit) || (m.is_not(lit, l) && is_uninterp_const(l)); } unsigned add_soft(expr* lit) { - SASSERT(is_literal(lit)); + //SASSERT(is_literal(lit)); unsigned idx = m_lit2expr.size(); m_expr2lit.insert(lit, idx); m_lit2expr.push_back(lit); @@ -64,7 +64,6 @@ struct mus::imp { } void add_assumption(expr* lit) { - SASSERT(is_literal(lit)); m_assumptions.push_back(lit); } @@ -78,17 +77,9 @@ struct mus::imp { return get_mus1(mus); } - lbool get_mus(ptr_vector& mus) { - mus.reset(); - expr_ref_vector result(m); - lbool r = get_mus(result); - mus.append(result.size(), result.c_ptr()); - return r; - } - lbool get_mus1(expr_ref_vector& mus) { ptr_vector unknown(m_lit2expr.size(), m_lit2expr.c_ptr()); - ptr_vector core_exprs; + expr_ref_vector core_exprs(m); TRACE("mus", m_solver.display(tout);); while (!unknown.empty()) { IF_VERBOSE(12, verbose_stream() << "(mus reducing core: " << unknown.size() << " new core: " << mus.size() << ")\n";); @@ -116,12 +107,12 @@ struct mus::imp { if (!core_exprs.contains(not_lit)) { // unknown := core_exprs \ mus unknown.reset(); - for (unsigned i = 0; i < core_exprs.size(); ++i) { - if (!mus.contains(core_exprs[i])) { - unknown.push_back(core_exprs[i]); + for (expr* c : core_exprs) { + if (!mus.contains(c)) { + unknown.push_back(c); } } - TRACE("mus", display_vec(tout << "core exprs:", core_exprs); + TRACE("mus", tout << "core exprs:" << core_exprs << "\n"; display_vec(tout << "core:", unknown); display_vec(tout << "mus:", mus); ); @@ -242,11 +233,11 @@ struct mus::imp { void get_core(expr_set& core) { core.reset(); - ptr_vector core_exprs; + expr_ref_vector core_exprs(m); m_solver.get_unsat_core(core_exprs); - for (unsigned i = 0; i < core_exprs.size(); ++i) { - if (m_expr2lit.contains(core_exprs[i])) { - core.insert(core_exprs[i]); + for (expr* c : core_exprs) { + if (m_expr2lit.contains(c)) { + core.insert(c); } } } @@ -341,8 +332,7 @@ struct mus::imp { m_solver.get_model(mdl); rational w; for (unsigned i = 0; i < m_soft.size(); ++i) { - mdl->eval(m_soft[i].get(), tmp); - if (!m.is_true(tmp)) { + if (!mdl->is_true(m_soft.get(i))) { w += m_weights[i]; } } @@ -375,9 +365,6 @@ void mus::add_assumption(expr* lit) { return m_imp->add_assumption(lit); } -lbool mus::get_mus(ptr_vector& mus) { - return m_imp->get_mus(mus); -} lbool mus::get_mus(expr_ref_vector& mus) { return m_imp->get_mus(mus); diff --git a/src/solver/mus.h b/src/solver/mus.h index f2e543f04..807b07873 100644 --- a/src/solver/mus.h +++ b/src/solver/mus.h @@ -47,8 +47,6 @@ class mus { */ void add_assumption(expr* lit); - lbool get_mus(ptr_vector& mus); - lbool get_mus(expr_ref_vector& mus); void reset(); diff --git a/src/solver/solver.cpp b/src/solver/solver.cpp index 4b812f83b..4044c4a85 100644 --- a/src/solver/solver.cpp +++ b/src/solver/solver.cpp @@ -44,7 +44,7 @@ std::ostream& solver::display(std::ostream & out, unsigned n, expr* const* assum ast_pp_util visitor(get_manager()); model_converter_ref mc = get_model_converter(); if (mc.get()) { - mc->collect(visitor); + mc->set_env(&visitor); } visitor.collect(fmls); visitor.collect(n, assumptions); @@ -52,6 +52,7 @@ std::ostream& solver::display(std::ostream & out, unsigned n, expr* const* assum visitor.display_asserts(out, fmls, true); if (mc.get()) { mc->display(out); + mc->set_env(nullptr); } return out; } @@ -63,6 +64,13 @@ void solver::get_assertions(expr_ref_vector& fmls) const { } } +expr_ref_vector solver::get_assertions() const { + expr_ref_vector result(get_manager()); + get_assertions(result); + return result; +} + + struct scoped_assumption_push { expr_ref_vector& m_vec; scoped_assumption_push(expr_ref_vector& v, expr* e): m_vec(v) { v.push_back(e); } @@ -206,8 +214,13 @@ void solver::collect_param_descrs(param_descrs & r) { r.insert("solver.enforce_model_conversion", CPK_BOOL, "(default: false) enforce model conversion when asserting formulas"); } -void solver::updt_params(params_ref const & p) { - m_params.copy(p); +void solver::reset_params(params_ref const & p) { + m_params = p; + m_enforce_model_conversion = m_params.get_bool("solver.enforce_model_conversion", false); +} + +void solver::updt_params(params_ref const & p) { + m_params.copy(p); m_enforce_model_conversion = m_params.get_bool("solver.enforce_model_conversion", false); } diff --git a/src/solver/solver.h b/src/solver/solver.h index 2c056ff90..c371be284 100644 --- a/src/solver/solver.h +++ b/src/solver/solver.h @@ -60,6 +60,11 @@ public: */ virtual void updt_params(params_ref const & p); + /** + \brief reset parameters. + */ + virtual void reset_params(params_ref const& p); + /** \brief Retrieve set of parameters set on solver. */ @@ -70,6 +75,17 @@ public: parameters available in this solver. */ virtual void collect_param_descrs(param_descrs & r); + + /** + \brief Push a parameter state. It is restored upon pop. + Only a single scope of push is supported. + */ + virtual void push_params() {} + + /** + \brief Pop a parameter state. \sa push_params. + */ + virtual void pop_params() {} /** \brief Enable/Disable model generation for this solver object. @@ -130,6 +146,16 @@ public: lbool check_sat(app_ref_vector const& asms) { return check_sat(asms.size(), (expr* const*)asms.c_ptr()); } + /** + \brief Check satisfiability modulo a cube and a clause. + + The cube corresponds to auxiliary assumptions. The clause as an auxiliary disjunction that is also + assumed for the check. + */ + virtual lbool check_sat_cc(expr_ref_vector const& cube, vector const& clauses) { + if (clauses.empty()) return check_sat(cube.size(), cube.c_ptr()); + NOT_IMPLEMENTED_YET(); + } /** \brief Set a progress callback procedure that is invoked by this solver during check_sat. @@ -153,6 +179,8 @@ public: */ void get_assertions(expr_ref_vector& fmls) const; + expr_ref_vector get_assertions() const; + /** \brief The number of tracked assumptions (see assert_expr(t, a)). */ @@ -229,4 +257,8 @@ protected: typedef ref solver_ref; +inline std::ostream& operator<<(std::ostream& out, solver const& s) { + return s.display(out); +} + #endif diff --git a/src/solver/solver2tactic.cpp b/src/solver/solver2tactic.cpp index 68d4b11f6..3c4f291a5 100644 --- a/src/solver/solver2tactic.cpp +++ b/src/solver/solver2tactic.cpp @@ -134,7 +134,7 @@ public: in->set(proof2proof_converter(m, pr)); } if (in->unsat_core_enabled()) { - ptr_vector core; + expr_ref_vector core(m); local_solver->get_unsat_core(core); for (expr* c : core) { lcore = m.mk_join(lcore, m.mk_leaf(bool2dep.find(c))); diff --git a/src/solver/solver_na2as.cpp b/src/solver/solver_na2as.cpp index f98e08cdb..db745597c 100644 --- a/src/solver/solver_na2as.cpp +++ b/src/solver/solver_na2as.cpp @@ -67,6 +67,12 @@ lbool solver_na2as::check_sat(unsigned num_assumptions, expr * const * assumptio return check_sat_core(m_assumptions.size(), m_assumptions.c_ptr()); } +lbool solver_na2as::check_sat_cc(const expr_ref_vector &assumptions, vector const &clauses) { + if (clauses.empty()) return check_sat(assumptions.size(), assumptions.c_ptr()); + append_assumptions app(m_assumptions, assumptions.size(), assumptions.c_ptr()); + return check_sat_cc_core(m_assumptions, clauses); +} + lbool solver_na2as::get_consequences(expr_ref_vector const& asms, expr_ref_vector const& vars, expr_ref_vector& consequences) { append_assumptions app(m_assumptions, asms.size(), asms.c_ptr()); return get_consequences_core(m_assumptions, vars, consequences); diff --git a/src/solver/solver_na2as.h b/src/solver/solver_na2as.h index 75fb27189..d1515a206 100644 --- a/src/solver/solver_na2as.h +++ b/src/solver/solver_na2as.h @@ -36,19 +36,21 @@ public: void assert_expr_core2(expr * t, expr * a) override; // virtual void assert_expr_core(expr * t) = 0; - + // Subclasses of solver_na2as should redefine the following *_core methods instead of these ones. lbool check_sat(unsigned num_assumptions, expr * const * assumptions) override; + lbool check_sat_cc(const expr_ref_vector &assumptions, vector const &clauses) override; void push() override; void pop(unsigned n) override; unsigned get_scope_level() const override; - + unsigned get_num_assumptions() const override { return m_assumptions.size(); } expr * get_assumption(unsigned idx) const override { return m_assumptions[idx]; } lbool get_consequences(expr_ref_vector const& asms, expr_ref_vector const& vars, expr_ref_vector& consequences) override; lbool find_mutexes(expr_ref_vector const& vars, vector& mutexes) override; protected: virtual lbool check_sat_core(unsigned num_assumptions, expr * const * assumptions) = 0; + virtual lbool check_sat_cc_core(const expr_ref_vector &assumptions, vector const &clauses) { NOT_IMPLEMENTED_YET(); } virtual void push_core() = 0; virtual void pop_core(unsigned n) = 0; }; diff --git a/src/solver/solver_pool.cpp b/src/solver/solver_pool.cpp index 6d08b95af..9dacfb5ce 100644 --- a/src/solver/solver_pool.cpp +++ b/src/solver/solver_pool.cpp @@ -32,8 +32,11 @@ class pool_solver : public solver_na2as { expr_ref_vector m_flat; bool m_pushed; bool m_in_delayed_scope; + bool m_dump_benchmarks; + double m_dump_threshold; unsigned m_dump_counter; + bool is_virtual() const { return !m.is_true(m_pred); } public: pool_solver(solver* b, solver_pool& pool, app_ref& pred): @@ -47,10 +50,13 @@ public: m_flat(m), m_pushed(false), m_in_delayed_scope(false), + m_dump_benchmarks(false), + m_dump_threshold(5.0), m_dump_counter(0) { if (is_virtual()) { solver_na2as::assert_expr_core2(m.mk_true(), pred); } + updt_params(m_base->get_params()); } ~pool_solver() override { @@ -64,16 +70,25 @@ public: solver* base_solver() { return m_base.get(); } solver* translate(ast_manager& m, params_ref const& p) override { UNREACHABLE(); return nullptr; } - void updt_params(params_ref const& p) override { solver::updt_params(p); m_base->updt_params(p); } + void updt_params(params_ref const& p) override { + solver::updt_params(p); m_base->updt_params(p); + m_dump_benchmarks = solver::get_params().get_bool("dump_benchmarks", false); + m_dump_threshold = solver::get_params().get_double("dump_threshold", 5.0); + } + void push_params() override {m_base->push_params();} + void pop_params() override {m_base->pop_params();} + void collect_param_descrs(param_descrs & r) override { m_base->collect_param_descrs(r); } void collect_statistics(statistics & st) const override { m_base->collect_statistics(st); } + unsigned get_num_assertions() const override { return m_base->get_num_assertions(); } + expr * get_assertion(unsigned idx) const override { return m_base->get_assertion(idx); } - void get_unsat_core(ptr_vector & r) override { + void get_unsat_core(expr_ref_vector& r) override { m_base->get_unsat_core(r); unsigned j = 0; - for (unsigned i = 0; i < r.size(); ++i) - if (m_pred != r[i]) - r[j++] = r[i]; + for (unsigned i = 0; i < r.size(); ++i) + if (m_pred != r.get(i)) + r[j++] = r.get(i); r.shrink(j); } @@ -82,6 +97,7 @@ public: return is_virtual() ? sz - 1 : sz; } + proof * get_proof() override { scoped_watch _t_(m_pool.m_proof_watch); if (!m_proof.get()) { @@ -125,35 +141,43 @@ public: break; } set_status(res); - - if (false /*m_dump_benchmarks && sw.get_seconds() >= m_pool.fparams().m_dump_min_time*/) { - std::stringstream file_name; - file_name << "virt_solver"; - if (is_virtual()) { file_name << "_" << m_pred->get_decl()->get_name(); } - file_name << "_" << (m_dump_counter++) << ".smt2"; - - std::ofstream out(file_name.str().c_str()); - if (!out) { verbose_stream() << "could not open file " << file_name.str() << " for output\n"; } - - out << "(set-info :status "; - switch (res) { - case l_true: out << "sat"; break; - case l_false: out << "unsat"; break; - case l_undef: out << "unknown"; break; - } - out << ")\n"; - m_base->display(out, num_assumptions, assumptions); - out << "(check-sat"; - for (unsigned i = 0; i < num_assumptions; ++i) { - out << " " << mk_pp(assumptions[i], m); - } - out << ")"; - out << "(exit)\n"; - ::statistics st; - m_base->collect_statistics(st); - st.update("time", sw.get_seconds()); - st.display_smt2(out); - out.close(); + + if (m_dump_benchmarks && sw.get_seconds() >= m_dump_threshold) { + expr_ref_vector cube(m, num_assumptions, assumptions); + vector clauses; + dump_benchmark(cube, clauses, res, sw.get_seconds()); + } + return res; + } + + lbool check_sat_cc_core(expr_ref_vector const & cube, + vector const & clauses) override { + SASSERT(!m_pushed || get_scope_level() > 0); + m_proof.reset(); + scoped_watch _t_(m_pool.m_check_watch); + m_pool.m_stats.m_num_checks++; + + stopwatch sw; + sw.start(); + internalize_assertions(); + lbool res = m_base->check_sat_cc(cube, clauses); + sw.stop(); + switch (res) { + case l_true: + m_pool.m_check_sat_watch.add(sw); + m_pool.m_stats.m_num_sat_checks++; + break; + case l_undef: + m_pool.m_check_undef_watch.add(sw); + m_pool.m_stats.m_num_undef_checks++; + break; + default: + break; + } + set_status(res); + + if (m_dump_benchmarks && sw.get_seconds() >= m_dump_threshold) { + dump_benchmark(cube, clauses, res, sw.get_seconds()); } return res; } @@ -167,41 +191,41 @@ public: m_pushed = true; m_in_delayed_scope = false; } - - if (!m_pushed) { - m_in_delayed_scope = true; + + if (!m_pushed) { + m_in_delayed_scope = true; } else { - SASSERT(m_pushed); SASSERT(!m_in_delayed_scope); m_base->push(); } } void pop_core(unsigned n) override { - SASSERT(!m_pushed || get_scope_level() > 0); + unsigned lvl = get_scope_level(); + SASSERT(!m_pushed || lvl > 0); if (m_pushed) { SASSERT(!m_in_delayed_scope); m_base->pop(n); - m_pushed = get_scope_level() - n > 0; - } - else { - m_in_delayed_scope = get_scope_level() - n > 0; + m_pushed = lvl - n > 0; + } + else { + m_in_delayed_scope = lvl - n > 0; } } - + void assert_expr_core(expr * e) override { SASSERT(!m_pushed || get_scope_level() > 0); - if (m.is_true(e)) return; + if (m.is_true(e)) return; if (m_in_delayed_scope) { internalize_assertions(); m_base->push(); m_pushed = true; m_in_delayed_scope = false; } - + if (m_pushed) { - m_base->assert_expr(e); + m_base->assert_expr(e); } else { m_flat.push_back(e); @@ -209,7 +233,7 @@ public: m_assertions.append(m_flat); m_flat.reset(); } - } + } void get_model_core(model_ref & _m) override { m_base->get_model_core(_m); } @@ -223,7 +247,7 @@ public: void set_progress_callback(progress_callback * callback) override { m_base->set_progress_callback(callback); } expr_ref_vector cube(expr_ref_vector& vars, unsigned ) override { return expr_ref_vector(m); } - + ast_manager& get_manager() const override { return m_base->get_manager(); } void refresh(solver* new_base) { @@ -238,13 +262,65 @@ public: m_assertions.reset(); m_pool.refresh(m_base.get()); } + +private: + + void dump_benchmark(const expr_ref_vector &cube, vector const & clauses, + lbool last_status, double last_time) { + std::string file_name = mk_file_name(); + std::ofstream out(file_name); + if (!out) { + IF_VERBOSE(0, verbose_stream() << "could not open file " << file_name << " for output\n"); + return; + } + + out << "(set-info :status " << lbool2status(last_status) << ")\n"; + m_base->display(out, cube.size(), cube.c_ptr()); + for (auto const& clause : clauses) { + out << ";; extra clause\n"; + out << "(assert (or "; + for (auto *lit : clause) out << mk_pp(lit, m) << " "; + out << "))\n"; + } + + out << "(check-sat"; + for (auto * lit : cube) out << " " << mk_pp(lit, m); + out << ")\n"; + + out << "(exit)\n"; + ::statistics st; + m_base->collect_statistics(st); + st.update("time", last_time); + st.display_smt2(out); + out.close(); + } + + char const* lbool2status(lbool r) const { + switch (r) { + case l_true: return "sat"; + case l_false: return "unsat"; + case l_undef: return "unknown"; + } + return "?"; + } + + std::string mk_file_name() { + std::stringstream file_name; + file_name << "pool_solver"; + if (is_virtual()) file_name << "_" << m_pred->get_decl()->get_name(); + file_name << "_" << (m_dump_counter++) << ".smt2"; + return file_name.str(); + } + }; -solver_pool::solver_pool(solver* base_solver, unsigned num_solvers_per_pool): +solver_pool::solver_pool(solver* base_solver, unsigned num_pools): m_base_solver(base_solver), - m_num_solvers_per_pool(num_solvers_per_pool), - m_num_solvers_in_last_pool(0) -{} + m_num_pools(num_pools), + m_current_pool(0) +{ + SASSERT(num_pools > 0); +} ptr_vector solver_pool::get_base_solvers() const { @@ -258,9 +334,13 @@ ptr_vector solver_pool::get_base_solvers() const { return solvers; } +void solver_pool::updt_params(const params_ref &p) { + m_base_solver->updt_params(p); + for (solver *s : m_solvers) s->updt_params(p); +} void solver_pool::collect_statistics(statistics &st) const { ptr_vector solvers = get_base_solvers(); - for (solver* s : solvers) s->collect_statistics(st); + for (solver* s : solvers) s->collect_statistics(st); st.update("time.pool_solver.smt.total", m_check_watch.get_seconds()); st.update("time.pool_solver.smt.total.sat", m_check_sat_watch.get_seconds()); st.update("time.pool_solver.smt.total.undef", m_check_undef_watch.get_seconds()); @@ -274,7 +354,7 @@ void solver_pool::reset_statistics() { #if 0 ptr_vector solvers = get_base_solvers(); for (solver* s : solvers) { - s->reset_statistics(); + s->reset_statistics(); } #endif m_stats.reset(); @@ -284,17 +364,23 @@ void solver_pool::reset_statistics() { m_proof_watch.reset(); } +/** + \brief Create a fresh solver instance. + The first num_pools solvers are independent and + use a fresh instance of the base solver. + Subsequent solvers reuse the first num_polls base solvers, rotating + among the first num_pools. +*/ solver* solver_pool::mk_solver() { ref base_solver; ast_manager& m = m_base_solver->get_manager(); - if (m_solvers.empty() || m_num_solvers_in_last_pool == m_num_solvers_per_pool) { + if (m_solvers.size() < m_num_pools) { base_solver = m_base_solver->translate(m, m_base_solver->get_params()); - m_num_solvers_in_last_pool = 0; } else { - base_solver = dynamic_cast(m_solvers.back())->base_solver(); + solver* s = m_solvers[(m_current_pool++) % m_num_pools]; + base_solver = dynamic_cast(s)->base_solver(); } - m_num_solvers_in_last_pool++; std::stringstream name; name << "vsolver#" << m_solvers.size(); app_ref pred(m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()), m); @@ -306,7 +392,7 @@ solver* solver_pool::mk_solver() { void solver_pool::reset_solver(solver* s) { pool_solver* ps = dynamic_cast(s); SASSERT(ps); - if (ps) ps->reset(); + if (ps) ps->reset(); } void solver_pool::refresh(solver* base_solver) { diff --git a/src/solver/solver_pool.h b/src/solver/solver_pool.h index d676ca54d..42c13fc58 100644 --- a/src/solver/solver_pool.h +++ b/src/solver/solver_pool.h @@ -16,7 +16,10 @@ Author: Notes: - This is a revision of spacer_virtual_solver by Arie Gurfinkel + This is a revision of spacer_virtual_solver + consolidated with spacer_smt_context_manager + by Arie Gurfinkel + Use this module as a replacement for spacer_smt_context_manager. --*/ #ifndef SOLVER_POOL_H_ @@ -39,11 +42,11 @@ class solver_pool { void reset() { memset(this, 0, sizeof(*this)); } }; - ref m_base_solver; - unsigned m_num_solvers_per_pool; - unsigned m_num_solvers_in_last_pool; + ref m_base_solver; + unsigned m_num_pools; + unsigned m_current_pool; sref_vector m_solvers; - stats m_stats; + stats m_stats; stopwatch m_check_watch; stopwatch m_check_sat_watch; @@ -55,7 +58,7 @@ class solver_pool { ptr_vector get_base_solvers() const; public: - solver_pool(solver* base_solver, unsigned num_solvers_per_pool); + solver_pool(solver* base_solver, unsigned num_pools); void collect_statistics(statistics &st) const; void reset_statistics(); @@ -63,6 +66,8 @@ public: solver* mk_solver(); void reset_solver(solver* s); + void updt_params(const params_ref &p); + }; diff --git a/src/solver/tactic2solver.cpp b/src/solver/tactic2solver.cpp index 9118bb658..94509e3f6 100644 --- a/src/solver/tactic2solver.cpp +++ b/src/solver/tactic2solver.cpp @@ -62,7 +62,7 @@ public: lbool check_sat_core(unsigned num_assumptions, expr * const * assumptions) override; void collect_statistics(statistics & st) const override; - void get_unsat_core(ptr_vector & r) override; + void get_unsat_core(expr_ref_vector & r) override; void get_model_core(model_ref & m) override; proof * get_proof() override; std::string reason_unknown() const override; @@ -219,7 +219,7 @@ void tactic2solver::collect_statistics(statistics & st) const { //SASSERT(m_stats.size() > 0); } -void tactic2solver::get_unsat_core(ptr_vector & r) { +void tactic2solver::get_unsat_core(expr_ref_vector & r) { if (m_result.get()) { m_result->get_unsat_core(r); } diff --git a/src/tactic/aig/aig.cpp b/src/tactic/aig/aig.cpp index 89196808f..faedf0e0c 100644 --- a/src/tactic/aig/aig.cpp +++ b/src/tactic/aig/aig.cpp @@ -490,7 +490,6 @@ struct aig_manager::imp { case OP_NOT: case OP_OR: case OP_AND: - case OP_IFF: case OP_XOR: case OP_IMPLIES: case OP_ITE: @@ -582,9 +581,6 @@ struct aig_manager::imp { SASSERT(m.m().is_bool(fr.m_t->get_arg(0))); mk_iff(fr.m_spos); break; - case OP_IFF: - mk_iff(fr.m_spos); - break; case OP_XOR: mk_xor(fr.m_spos); break; diff --git a/src/tactic/arith/fm_tactic.cpp b/src/tactic/arith/fm_tactic.cpp index 679adb2dd..fc41f54a4 100644 --- a/src/tactic/arith/fm_tactic.cpp +++ b/src/tactic/arith/fm_tactic.cpp @@ -62,7 +62,7 @@ class fm_tactic : public tactic { return m.is_false(val); } - r_kind process(func_decl * x, expr * cls, arith_util & u, model_evaluator & ev, rational & r) { + r_kind process(func_decl * x, expr * cls, arith_util & u, model& ev, rational & r) { unsigned num_lits; expr * const * lits; if (m.is_or(cls)) { @@ -80,9 +80,7 @@ class fm_tactic : public tactic { expr * l = lits[i]; expr * atom; if (is_uninterp_const(l) || (m.is_not(l, atom) && is_uninterp_const(atom))) { - expr_ref val(m); - ev(l, val); - if (m.is_true(val)) + if (ev.is_true(l)) return NONE; // clause was satisfied } else { @@ -131,7 +129,7 @@ class fm_tactic : public tactic { } else { expr_ref val(m); - ev(monomial, val); + val = ev(monomial); SASSERT(u.is_numeral(val)); rational tmp; u.is_numeral(val, tmp); @@ -184,8 +182,9 @@ class fm_tactic : public tactic { void operator()(model_ref & md) override { TRACE("fm_mc", model_v2_pp(tout, *md); display(tout);); - model_evaluator ev(*(md.get())); - ev.set_model_completion(true); + model::scoped_model_completion _sc(*md, true); + //model_evaluator ev(*(md.get())); + //ev.set_model_completion(true); arith_util u(m); unsigned i = m_xs.size(); while (i > 0) { @@ -201,7 +200,7 @@ class fm_tactic : public tactic { clauses::iterator end = m_clauses[i].end(); for (; it != end; ++it) { if (m.canceled()) throw tactic_exception(m.limit().get_cancel_msg()); - switch (process(x, *it, u, ev, val)) { + switch (process(x, *it, u, *md, val)) { case NONE: TRACE("fm_mc", tout << "no bound for:\n" << mk_ismt2_pp(*it, m) << "\n";); break; diff --git a/src/tactic/arith/lia2pb_tactic.cpp b/src/tactic/arith/lia2pb_tactic.cpp index 178ace7fd..db1c22866 100644 --- a/src/tactic/arith/lia2pb_tactic.cpp +++ b/src/tactic/arith/lia2pb_tactic.cpp @@ -162,10 +162,8 @@ class lia2pb_tactic : public tactic { } bool has_target() { - bound_manager::iterator it = m_bm.begin(); - bound_manager::iterator end = m_bm.end(); - for (; it != end; ++it) { - if (is_target(*it)) + for (expr * x : m_bm) { + if (is_target(x)) return true; } return false; @@ -174,10 +172,7 @@ class lia2pb_tactic : public tactic { bool check_num_bits() { unsigned num_bits = 0; rational u; - bound_manager::iterator it = m_bm.begin(); - bound_manager::iterator end = m_bm.end(); - for (; it != end; ++it) { - expr * x = *it; + for (expr * x : m_bm) { if (is_target_core(x, u) && u > rational(1)) { num_bits += u.get_num_bits(); if (num_bits > m_total_bits) @@ -234,10 +229,7 @@ class lia2pb_tactic : public tactic { expr_substitution subst(m, m_produce_unsat_cores, false); rational u; ptr_buffer def_args; - bound_manager::iterator it = m_bm.begin(); - bound_manager::iterator end = m_bm.end(); - for (; it != end; ++it) { - expr * x = *it; + for (expr * x : m_bm) { if (is_target_core(x, u) && u > rational(1)) { num_converted++; def_args.reset(); @@ -251,7 +243,7 @@ class lia2pb_tactic : public tactic { def_args.push_back(x_prime); else def_args.push_back(m_util.mk_mul(m_util.mk_numeral(a, true), x_prime)); - if (m_produce_models) + if (m_produce_models) gmc->hide(x_prime->get_decl()); a *= rational(2); } diff --git a/src/tactic/arith/normalize_bounds_tactic.cpp b/src/tactic/arith/normalize_bounds_tactic.cpp index 907c7af8c..60d583798 100644 --- a/src/tactic/arith/normalize_bounds_tactic.cpp +++ b/src/tactic/arith/normalize_bounds_tactic.cpp @@ -109,8 +109,8 @@ class normalize_bounds_tactic : public tactic { expr * def = m_util.mk_add(x_prime, m_util.mk_numeral(val, s)); subst.insert(x, def); if (produce_models) { - gmc->add(to_app(x)->get_decl(), def); gmc->hide(x_prime->get_decl()); + gmc->add(to_app(x)->get_decl(), def); } } } diff --git a/src/tactic/core/blast_term_ite_tactic.cpp b/src/tactic/core/blast_term_ite_tactic.cpp index 3c4684cb8..eefb9418e 100644 --- a/src/tactic/core/blast_term_ite_tactic.cpp +++ b/src/tactic/core/blast_term_ite_tactic.cpp @@ -33,50 +33,65 @@ Notes: class blast_term_ite_tactic : public tactic { struct rw_cfg : public default_rewriter_cfg { - ast_manager& m; - unsigned long long m_max_memory; // in bytes - unsigned m_num_fresh; // number of expansions + ast_manager& m; + unsigned long long m_max_memory; // in bytes + unsigned m_num_fresh; // number of expansions + unsigned m_max_steps; + unsigned m_max_inflation; + unsigned m_init_term_size; rw_cfg(ast_manager & _m, params_ref const & p): m(_m), - m_num_fresh(0) { + m_num_fresh(0), + m_max_steps(UINT_MAX), + m_max_inflation(UINT_MAX), + m_init_term_size(0) { updt_params(p); } void updt_params(params_ref const & p) { - m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); + m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); + m_max_steps = p.get_uint("max_steps", UINT_MAX); + m_max_inflation = p.get_uint("max_inflation", UINT_MAX); // multiplicative factor of initial term size. } + + bool max_steps_exceeded(unsigned num_steps) const { cooperate("blast term ite"); // if (memory::get_allocation_size() > m_max_memory) // throw tactic_exception(TACTIC_MAX_MEMORY_MSG); - return false; + return num_steps >= m_max_steps; } br_status mk_app_core(func_decl* f, unsigned num_args, expr* const* args, expr_ref& result) { if (m.is_ite(f)) { return BR_FAILED; } + if (m_max_inflation < UINT_MAX && + m_init_term_size > 0 && + m_max_inflation * m_init_term_size < m_num_fresh) + return BR_FAILED; + for (unsigned i = 0; i < num_args; ++i) { expr* c, *t, *e; if (!m.is_bool(args[i]) && m.is_ite(args[i], c, t, e)) { - // enable_trace("blast_term_ite"); TRACE("blast_term_ite", result = m.mk_app(f, num_args, args); tout << result << "\n";); expr_ref e1(m), e2(m); ptr_vector args1(num_args, args); args1[i] = t; - ++m_num_fresh; e1 = m.mk_app(f, num_args, args1.c_ptr()); - if (m.are_equal(t,e)) { + if (m.are_equal(t, e)) { result = e1; return BR_REWRITE1; } - args1[i] = e; - e2 = m.mk_app(f, num_args, args1.c_ptr()); - result = m.mk_app(f, num_args, args); - result = m.mk_ite(c, e1, e2); - return BR_REWRITE3; + else { + args1[i] = e; + e2 = m.mk_app(f, num_args, args1.c_ptr()); + result = m.mk_ite(c, e1, e2); + ++m_num_fresh; + return BR_REWRITE3; + } } } return BR_FAILED; @@ -107,10 +122,9 @@ class blast_term_ite_tactic : public tactic { m(_m), m_rw(m, p) { } - - + void updt_params(params_ref const & p) { - m_rw.cfg().updt_params(p); + m_rw.m_cfg.updt_params(p); } void operator()(goal_ref const & g, goal_ref_buffer & result) { @@ -121,8 +135,14 @@ class blast_term_ite_tactic : public tactic { expr_ref new_curr(m); proof_ref new_pr(m); unsigned size = g->size(); + unsigned num_fresh = 0; for (unsigned idx = 0; idx < size; idx++) { expr * curr = g->form(idx); + if (m_rw.m_cfg.m_max_inflation < UINT_MAX) { + m_rw.m_cfg.m_init_term_size = get_num_exprs(curr); + num_fresh += m_rw.m_cfg.m_num_fresh; + m_rw.m_cfg.m_num_fresh = 0; + } m_rw(curr, new_curr, new_pr); if (produce_proofs) { proof * pr = g->pr(idx); @@ -130,7 +150,7 @@ class blast_term_ite_tactic : public tactic { } g->update(idx, new_curr, new_pr, g->dep(idx)); } - report_tactic_progress(":blast-term-ite-consts", m_rw.m_cfg.m_num_fresh); + report_tactic_progress(":blast-term-ite-consts", m_rw.m_cfg.m_num_fresh + num_fresh); g->inc_depth(); result.push_back(g.get()); TRACE("blast_term_ite", g->display(tout);); @@ -156,7 +176,7 @@ public: void updt_params(params_ref const & p) override { m_params = p; - m_imp->m_rw.cfg().updt_params(p); + m_imp->m_rw.m_cfg.updt_params(p); } void collect_param_descrs(param_descrs & r) override { @@ -176,14 +196,23 @@ public: m_imp = alloc(imp, m, m_params); } - static void blast_term_ite(expr_ref& fml) { + static void blast_term_ite(expr_ref& fml, unsigned max_inflation) { ast_manager& m = fml.get_manager(); scoped_no_proof _sp(m); params_ref p; rw ite_rw(m, p); - expr_ref tmp(m); - ite_rw(fml, tmp); - fml = tmp; + ite_rw.m_cfg.m_max_inflation = max_inflation; + if (max_inflation < UINT_MAX) { + ite_rw.m_cfg.m_init_term_size = get_num_exprs(fml); + } + try { + expr_ref tmp(m); + ite_rw(fml, tmp); + fml = tmp; + } + catch (z3_exception &) { + // max steps exceeded. + } } }; @@ -191,6 +220,6 @@ tactic * mk_blast_term_ite_tactic(ast_manager & m, params_ref const & p) { return clean(alloc(blast_term_ite_tactic, m, p)); } -void blast_term_ite(expr_ref& fml) { - blast_term_ite_tactic::blast_term_ite(fml); +void blast_term_ite(expr_ref& fml, unsigned max_inflation) { + blast_term_ite_tactic::blast_term_ite(fml, max_inflation); } diff --git a/src/tactic/core/blast_term_ite_tactic.h b/src/tactic/core/blast_term_ite_tactic.h index 1ecab98be..fcad6c068 100644 --- a/src/tactic/core/blast_term_ite_tactic.h +++ b/src/tactic/core/blast_term_ite_tactic.h @@ -33,6 +33,6 @@ tactic * mk_blast_term_ite_tactic(ast_manager & m, params_ref const & p = params ADD_TACTIC("blast-term-ite", "blast term if-then-else by hoisting them.", "mk_blast_term_ite_tactic(m, p)") */ -void blast_term_ite(expr_ref& fml); +void blast_term_ite(expr_ref& fml, unsigned max_inflation); #endif diff --git a/src/tactic/core/cofactor_elim_term_ite.cpp b/src/tactic/core/cofactor_elim_term_ite.cpp index 2b3cb8414..1b435791c 100644 --- a/src/tactic/core/cofactor_elim_term_ite.cpp +++ b/src/tactic/core/cofactor_elim_term_ite.cpp @@ -87,7 +87,6 @@ struct cofactor_elim_term_ite::imp { case OP_TRUE: case OP_FALSE: case OP_ITE: - case OP_IFF: return; case OP_EQ: case OP_DISTINCT: diff --git a/src/tactic/core/dom_simplify_tactic.cpp b/src/tactic/core/dom_simplify_tactic.cpp index 43da3bc00..27a5bdc94 100644 --- a/src/tactic/core/dom_simplify_tactic.cpp +++ b/src/tactic/core/dom_simplify_tactic.cpp @@ -486,7 +486,7 @@ bool expr_substitution_simplifier::is_gt(expr* lhs, expr* rhs) { void expr_substitution_simplifier::update_substitution(expr* n, proof* pr) { expr* lhs, *rhs, *n1; - if (is_ground(n) && (m.is_eq(n, lhs, rhs) || m.is_iff(n, lhs, rhs))) { + if (is_ground(n) && m.is_eq(n, lhs, rhs)) { compute_depth(lhs); compute_depth(rhs); m_trail.push_back(lhs); diff --git a/src/tactic/core/elim_uncnstr_tactic.cpp b/src/tactic/core/elim_uncnstr_tactic.cpp index ce77f89d9..577db30cd 100644 --- a/src/tactic/core/elim_uncnstr_tactic.cpp +++ b/src/tactic/core/elim_uncnstr_tactic.cpp @@ -331,7 +331,6 @@ class elim_uncnstr_tactic : public tactic { return r; } return nullptr; - case OP_IFF: case OP_EQ: SASSERT(num == 2); return process_eq(f, args[0], args[1]); diff --git a/src/tactic/core/solve_eqs_tactic.cpp b/src/tactic/core/solve_eqs_tactic.cpp index acd75d663..f665fe509 100644 --- a/src/tactic/core/solve_eqs_tactic.cpp +++ b/src/tactic/core/solve_eqs_tactic.cpp @@ -344,10 +344,7 @@ class solve_eqs_tactic : public tactic { } return false; } - - if (m().is_iff(f)) - return trivial_solve(to_app(f)->get_arg(0), to_app(f)->get_arg(1), var, def, pr); - + #if 0 if (not_bool_eq(f, var, def, pr)) return true; @@ -639,7 +636,7 @@ class solve_eqs_tactic : public tactic { TRACE("gaussian_leak", tout << "processing:\n" << mk_ismt2_pp(f, m()) << "\n";); if (m_candidate_set.is_marked(f)) { // f may be deleted after the following update. - // so, we must remove remove the mark before doing the update + // so, we must remove the mark before doing the update m_candidate_set.mark(f, false); SASSERT(!m_candidate_set.is_marked(f)); g.update(idx, m().mk_true(), m().mk_true_proof(), nullptr); @@ -845,4 +842,3 @@ tactic * mk_solve_eqs_tactic(ast_manager & m, params_ref const & p, expr_replace else return clean(alloc(solve_eqs_tactic, m, p, r, false)); } - diff --git a/src/tactic/core/tseitin_cnf_tactic.cpp b/src/tactic/core/tseitin_cnf_tactic.cpp index 2abaae0e6..b29a80927 100644 --- a/src/tactic/core/tseitin_cnf_tactic.cpp +++ b/src/tactic/core/tseitin_cnf_tactic.cpp @@ -176,7 +176,6 @@ class tseitin_cnf_tactic : public tactic { sign = !sign; goto start; case OP_OR: - case OP_IFF: l = nullptr; m_cache.find(to_app(n), l); SASSERT(l != 0); @@ -223,7 +222,6 @@ class tseitin_cnf_tactic : public tactic { goto start; } case OP_OR: - case OP_IFF: visited = false; push_frame(to_app(n)); return; @@ -468,6 +466,38 @@ class tseitin_cnf_tactic : public tactic { } return NO; } + + mres match_iff_or(app * t, bool first, bool root) { + expr * a = nullptr, * _b = nullptr; + if (!root) return NO; + if (!is_iff(m, t, a, _b)) return NO; + bool sign = m.is_not(_b, _b); + if (!m.is_or(_b)) return NO; + app* b = to_app(_b); + unsigned num = b->get_num_args(); + if (first) { + bool visited = true; + visit(a, visited); + for (expr* arg : *b) { + visit(arg, visited); + } + if (!visited) + return CONT; + } + expr_ref la(m), nla(m), nlb(m), lb(m); + get_lit(a, sign, la); + inv(la, nla); + expr_ref_buffer lits(m); + lits.push_back(nla); + for (expr* arg : *b) { + get_lit(arg, false, lb); + lits.push_back(lb); + inv(lb, nlb); + mk_clause(la, nlb); + } + mk_clause(lits.size(), lits.c_ptr()); + return DONE; + } mres match_iff(app * t, bool first, bool root) { expr * a, * b; @@ -786,6 +816,7 @@ class tseitin_cnf_tactic : public tactic { TRY(match_or_3and); TRY(match_or); TRY(match_iff3); + // TRY(match_iff_or); TRY(match_iff); TRY(match_ite); TRY(match_not); diff --git a/src/tactic/generic_model_converter.cpp b/src/tactic/generic_model_converter.cpp index 9e2a7b9b2..0790c962a 100644 --- a/src/tactic/generic_model_converter.cpp +++ b/src/tactic/generic_model_converter.cpp @@ -41,7 +41,7 @@ void generic_model_converter::add(func_decl * d, expr* e) { void generic_model_converter::operator()(model_ref & md) { TRACE("model_converter", tout << "before generic_model_converter\n"; model_v2_pp(tout, *md); display(tout);); - // IF_VERBOSE(0, verbose_stream() << "Apply converter " << m_orig << "\n";); + model_evaluator ev(*(md.get())); ev.set_model_completion(true); ev.set_expand_array_equalities(false); @@ -114,11 +114,16 @@ model_converter * generic_model_converter::translate(ast_translation & translato return res; } -void generic_model_converter::collect(ast_pp_util& visitor) { - m_env = &visitor.env(); - for (entry const& e : m_entries) { - visitor.coll.visit_func(e.m_f); - if (e.m_def) visitor.coll.visit(e.m_def); +void generic_model_converter::set_env(ast_pp_util* visitor) { + if (!visitor) { + m_env = nullptr; + } + else { + m_env = &visitor->env(); + for (entry const& e : m_entries) { + visitor->coll.visit_func(e.m_f); + if (e.m_def) visitor->coll.visit(e.m_def); + } } } diff --git a/src/tactic/generic_model_converter.h b/src/tactic/generic_model_converter.h index d303fb2e3..a190b8525 100644 --- a/src/tactic/generic_model_converter.h +++ b/src/tactic/generic_model_converter.h @@ -68,7 +68,7 @@ public: model_converter * translate(ast_translation & translator) override; - void collect(ast_pp_util& visitor) override; + void set_env(ast_pp_util* visitor) override; void operator()(expr_ref& fml) override; diff --git a/src/tactic/horn_subsume_model_converter.cpp b/src/tactic/horn_subsume_model_converter.cpp index 0d49a769e..eeb2967f2 100644 --- a/src/tactic/horn_subsume_model_converter.cpp +++ b/src/tactic/horn_subsume_model_converter.cpp @@ -195,8 +195,7 @@ void horn_subsume_model_converter::operator()(model_ref& mr) { SASSERT(m.is_bool(body)); TRACE("mc", tout << "eval: " << h->get_name() << "\n" << body << "\n";); - expr_ref tmp(body); - mr->eval(tmp, body); + body = (*mr)(body); TRACE("mc", tout << "to:\n" << body << "\n";); diff --git a/src/tactic/model_converter.cpp b/src/tactic/model_converter.cpp index b39940366..89e2b6602 100644 --- a/src/tactic/model_converter.cpp +++ b/src/tactic/model_converter.cpp @@ -43,6 +43,16 @@ void model_converter::display_del(std::ostream& out, func_decl* f) const { } } +void model_converter::set_env(ast_pp_util* visitor) { + if (visitor) { + m_env = &visitor->env(); + } + else { + m_env = nullptr; + } +} + + void model_converter::display_add(std::ostream& out, ast_manager& m) { // default printer for converter that adds entries model_ref mdl = alloc(model, m); @@ -91,9 +101,9 @@ public: return this->translate_core(translator); } - void collect(ast_pp_util& visitor) override { - this->m_c1->collect(visitor); - this->m_c2->collect(visitor); + void set_env(ast_pp_util* visitor) override { + this->m_c1->set_env(visitor); + this->m_c2->set_env(visitor); } }; @@ -125,9 +135,8 @@ public: } void operator()(expr_ref& fml) override { - expr_ref r(m_model->get_manager()); - m_model->eval(fml, r, false); - fml = r; + model::scoped_model_completion _scm(m_model, false); + fml = (*m_model)(fml); } void get_units(obj_map& fmls) override { diff --git a/src/tactic/model_converter.h b/src/tactic/model_converter.h index cd8fb5ee3..9c5b72830 100644 --- a/src/tactic/model_converter.h +++ b/src/tactic/model_converter.h @@ -80,7 +80,7 @@ public: virtual model_converter * translate(ast_translation & translator) = 0; - virtual void collect(ast_pp_util& visitor) { m_env = &visitor.env(); } + virtual void set_env(ast_pp_util* visitor); /** \brief we are adding a formula to the context of the model converter. diff --git a/src/tactic/portfolio/bounded_int2bv_solver.cpp b/src/tactic/portfolio/bounded_int2bv_solver.cpp index 8767644f3..6389ed739 100644 --- a/src/tactic/portfolio/bounded_int2bv_solver.cpp +++ b/src/tactic/portfolio/bounded_int2bv_solver.cpp @@ -148,7 +148,7 @@ public: void set_produce_models(bool f) override { m_solver->set_produce_models(f); } void set_progress_callback(progress_callback * callback) override { m_solver->set_progress_callback(callback); } void collect_statistics(statistics & st) const override { m_solver->collect_statistics(st); } - void get_unsat_core(ptr_vector & r) override { m_solver->get_unsat_core(r); } + void get_unsat_core(expr_ref_vector & r) override { m_solver->get_unsat_core(r); } void get_model_core(model_ref & mdl) override { m_solver->get_model(mdl); if (mdl) { diff --git a/src/tactic/portfolio/default_tactic.cpp b/src/tactic/portfolio/default_tactic.cpp index 51cda17cd..7f71114f4 100644 --- a/src/tactic/portfolio/default_tactic.cpp +++ b/src/tactic/portfolio/default_tactic.cpp @@ -31,9 +31,11 @@ Notes: #include "tactic/fpa/qffplra_tactic.h" #include "tactic/smtlogics/qfaufbv_tactic.h" #include "tactic/smtlogics/qfauflia_tactic.h" +#include "tactic/portfolio/fd_solver.h" tactic * mk_default_tactic(ast_manager & m, params_ref const & p) { tactic * st = using_params(and_then(mk_simplify_tactic(m), + cond(mk_is_propositional_probe(), if_no_proofs(mk_fd_tactic(m, p)), cond(mk_is_qfbv_probe(), mk_qfbv_tactic(m), cond(mk_is_qfaufbv_probe(), mk_qfaufbv_tactic(m), cond(mk_is_qflia_probe(), mk_qflia_tactic(m), @@ -46,7 +48,7 @@ tactic * mk_default_tactic(ast_manager & m, params_ref const & p) { cond(mk_is_qffp_probe(), mk_qffp_tactic(m, p), cond(mk_is_qffplra_probe(), mk_qffplra_tactic(m, p), //cond(mk_is_qfufnra_probe(), mk_qfufnra_tactic(m, p), - mk_smt_tactic())))))))))))), + mk_smt_tactic()))))))))))))), p); return st; } diff --git a/src/tactic/portfolio/enum2bv_solver.cpp b/src/tactic/portfolio/enum2bv_solver.cpp index 6b5fe9056..29c6aeb39 100644 --- a/src/tactic/portfolio/enum2bv_solver.cpp +++ b/src/tactic/portfolio/enum2bv_solver.cpp @@ -88,7 +88,7 @@ public: void set_produce_models(bool f) override { m_solver->set_produce_models(f); } void set_progress_callback(progress_callback * callback) override { m_solver->set_progress_callback(callback); } void collect_statistics(statistics & st) const override { m_solver->collect_statistics(st); } - void get_unsat_core(ptr_vector & r) override { m_solver->get_unsat_core(r); } + void get_unsat_core(expr_ref_vector & r) override { m_solver->get_unsat_core(r); } void get_model_core(model_ref & mdl) override { m_solver->get_model(mdl); if (mdl) { diff --git a/src/tactic/portfolio/pb2bv_solver.cpp b/src/tactic/portfolio/pb2bv_solver.cpp index f8794ca41..60ca6a5dc 100644 --- a/src/tactic/portfolio/pb2bv_solver.cpp +++ b/src/tactic/portfolio/pb2bv_solver.cpp @@ -87,7 +87,7 @@ public: m_rewriter.collect_statistics(st); m_solver->collect_statistics(st); } - void get_unsat_core(ptr_vector & r) override { m_solver->get_unsat_core(r); } + void get_unsat_core(expr_ref_vector & r) override { m_solver->get_unsat_core(r); } void get_model_core(model_ref & mdl) override { m_solver->get_model(mdl); if (mdl) { diff --git a/src/tactic/sine_filter.cpp b/src/tactic/sine_filter.cpp index 0ac726986..647791784 100644 --- a/src/tactic/sine_filter.cpp +++ b/src/tactic/sine_filter.cpp @@ -93,11 +93,8 @@ private: obj_hashtable const & consts, ptr_vector & next_consts) { TRACE("sine", - tout << "size of consts is "; tout << consts.size(); tout << "\n"; - obj_hashtable::iterator it = consts.begin(); - obj_hashtable::iterator end = consts.end(); - for (; it != end; it++) - tout << *it << "\n"; ); + tout << "size of consts is "; tout << consts.size(); tout << "\n"; + for (func_decl* f : consts) tout << f->get_name() << "\n";); bool matched = false; for (unsigned i = 0; i < q->get_num_patterns(); i++) { @@ -156,10 +153,8 @@ private: if (!consts.contains(f)) { consts.insert(f); if (const2quantifier.contains(f)) { - obj_pair_hashtable::iterator it = const2quantifier[f].begin(); - obj_pair_hashtable::iterator end = const2quantifier[f].end(); - for (; it != end; it++) - stack.push_back(*it); + for (auto const& p : const2quantifier[f]) + stack.push_back(p); const2quantifier.remove(f); } } @@ -220,16 +215,11 @@ private: visiting = to_visit.back(); to_visit.pop_back(); visited.insert(visiting); - obj_hashtable::iterator it = exp2const[visiting].begin(); - obj_hashtable::iterator end = exp2const[visiting].end(); - for (; it != end; it++) { - obj_hashtable::iterator exprit = const2exp[*it].begin(); - obj_hashtable::iterator exprend = const2exp[*it].end(); - for (; exprit != exprend; exprit++) { - if (!visited.contains(*exprit)) - to_visit.push_back(*exprit); + for (func_decl* f : exp2const[visiting]) + for (expr* e : const2exp[f]) { + if (!visited.contains(e)) + to_visit.push_back(e); } - } } for (unsigned i = 0; i < g->size(); i++) { diff --git a/src/tactic/sls/sls_engine.cpp b/src/tactic/sls/sls_engine.cpp index 7836ab70f..f5b5ec1b2 100644 --- a/src/tactic/sls/sls_engine.cpp +++ b/src/tactic/sls/sls_engine.cpp @@ -100,36 +100,27 @@ void sls_engine::checkpoint() { } bool sls_engine::full_eval(model & mdl) { - bool res = true; - - unsigned sz = m_assertions.size(); - for (unsigned i = 0; i < sz && res; i++) { - checkpoint(); - expr_ref o(m_manager); - - if (!mdl.eval(m_assertions[i], o, true)) - exit(ERR_INTERNAL_FATAL); - - res = m_manager.is_true(o.get()); - } - - TRACE("sls", tout << "Evaluation: " << res << std::endl;); - - return res; + model::scoped_model_completion _scm(mdl, true); + for (expr* a : m_assertions) { + checkpoint(); + if (!mdl.is_true(a)) { + TRACE("sls", tout << "Evaluation: false\n";); + return false; + } + } + return true; } double sls_engine::top_score() { double top_sum = 0.0; - unsigned sz = m_assertions.size(); - for (unsigned i = 0; i < sz; i++) { - expr * e = m_assertions[i]; + for (expr* e : m_assertions) { top_sum += m_tracker.get_score(e); } TRACE("sls_top", tout << "Score distribution:"; - for (unsigned i = 0; i < sz; i++) - tout << " " << m_tracker.get_score(m_assertions[i]); - tout << " AVG: " << top_sum / (double)sz << std::endl;); + for (expr* e : m_assertions) + tout << " " << m_tracker.get_score(e); + tout << " AVG: " << top_sum / (double)m_assertions.size() << std::endl;); m_tracker.set_top_sum(top_sum); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index b487fe9ba..7367a52ff 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(test-z3 chashtable.cpp check_assumptions.cpp cnf_backbones.cpp + cube_clause.cpp datalog_parser.cpp ddnf.cpp diff_logic.cpp @@ -80,7 +81,6 @@ add_executable(test-z3 optional.cpp parray.cpp pb2bv.cpp - pdr.cpp permutation.cpp polynomial.cpp polynorm.cpp @@ -102,6 +102,7 @@ add_executable(test-z3 small_object_allocator.cpp smt2print_parse.cpp smt_context.cpp + solver_pool.cpp sorting_network.cpp stack.cpp string_buffer.cpp diff --git a/src/test/arith_rewriter.cpp b/src/test/arith_rewriter.cpp index a279d58b2..d0a110c4f 100644 --- a/src/test/arith_rewriter.cpp +++ b/src/test/arith_rewriter.cpp @@ -10,7 +10,6 @@ Copyright (c) 2015 Microsoft Corporation #include "ast/reg_decl_plugins.h" #include "ast/rewriter/th_rewriter.h" #include "model/model.h" -#include "muz/pdr/pdr_util.h" #include "parsers/smt2/smt2parser.h" @@ -53,13 +52,9 @@ void tst_arith_rewriter() { expr_ref fml = parse_fml(m, example1); rw(fml); std::cout << mk_pp(fml, m) << "\n"; - pdr::normalize_arithmetic(fml); - std::cout << mk_pp(fml, m) << "\n"; fml = parse_fml(m, example2); rw(fml); std::cout << mk_pp(fml, m) << "\n"; - pdr::normalize_arithmetic(fml); - std::cout << mk_pp(fml, m) << "\n"; } diff --git a/src/test/cube_clause.cpp b/src/test/cube_clause.cpp new file mode 100644 index 000000000..0c783277b --- /dev/null +++ b/src/test/cube_clause.cpp @@ -0,0 +1,66 @@ +#include "ast/reg_decl_plugins.h" +#include "solver/solver_pool.h" +#include "smt/smt_solver.h" + + +void tst_cube_clause() { + ast_manager m; + reg_decl_plugins(m); + params_ref p; + lbool r; + ref solver = mk_smt_solver(m, p, symbol::null); + + expr_ref a(m.mk_const(symbol("a"), m.mk_bool_sort()), m); + expr_ref b(m.mk_const(symbol("b"), m.mk_bool_sort()), m); + expr_ref c(m.mk_const(symbol("c"), m.mk_bool_sort()), m); + expr_ref d(m.mk_const(symbol("d"), m.mk_bool_sort()), m); + expr_ref e(m.mk_const(symbol("e"), m.mk_bool_sort()), m); + expr_ref f(m.mk_const(symbol("f"), m.mk_bool_sort()), m); + expr_ref g(m.mk_const(symbol("g"), m.mk_bool_sort()), m); + expr_ref fml(m); + fml = m.mk_not(m.mk_and(a, b)); + solver->assert_expr(fml); + fml = m.mk_not(m.mk_and(c, d)); + solver->assert_expr(fml); + fml = m.mk_not(m.mk_and(e, f)); + solver->assert_expr(fml); + expr_ref_vector cube(m), clause(m), core(m); + r = solver->check_sat(cube); + std::cout << r << "\n"; + cube.push_back(a); + r = solver->check_sat(cube); + std::cout << r << "\n"; + cube.push_back(c); + cube.push_back(e); + r = solver->check_sat(cube); + std::cout << r << "\n"; + clause.push_back(b); + vector clauses; + clauses.push_back(clause); + r = solver->check_sat_cc(cube, clauses); + std::cout << r << "\n"; + core.reset(); + solver->get_unsat_core(core); + std::cout << core << "\n"; + clause.push_back(d); + clauses.reset(); + clauses.push_back(clause); + r = solver->check_sat_cc(cube, clauses); + std::cout << r << "\n"; + core.reset(); + solver->get_unsat_core(core); + std::cout << core << "\n"; + clause.push_back(f); + clauses.reset(); + clauses.push_back(clause); + r = solver->check_sat_cc(cube, clauses); + std::cout << r << "\n"; + core.reset(); + solver->get_unsat_core(core); + std::cout << core << "\n"; + clause.push_back(g); + clauses.reset(); + clauses.push_back(clause); + r = solver->check_sat_cc(cube, clauses); + std::cout << r << "\n"; +} diff --git a/src/test/main.cpp b/src/test/main.cpp index 64f754667..d330610d9 100644 --- a/src/test/main.cpp +++ b/src/test/main.cpp @@ -172,6 +172,7 @@ int main(int argc, char ** argv) { TST(var_subst); TST(simple_parser); TST(api); + TST(cube_clause); TST(old_interval); TST(get_implied_equalities); TST(arith_simplifier_plugin); @@ -238,7 +239,6 @@ int main(int argc, char ** argv) { TST(theory_pb); TST(simplex); TST(sat_user_scope); - TST(pdr); TST_ARGV(ddnf); TST(ddnf1); TST(model_evaluator); @@ -248,6 +248,7 @@ int main(int argc, char ** argv) { TST_ARGV(sat_local_search); TST_ARGV(cnf_backbones); TST(bdd); + TST(solver_pool); //TST_ARGV(hs); } diff --git a/src/test/model_based_opt.cpp b/src/test/model_based_opt.cpp index 7b3b95afd..1bae7435f 100644 --- a/src/test/model_based_opt.cpp +++ b/src/test/model_based_opt.cpp @@ -218,6 +218,12 @@ static void test4() { std::cout << "u: " << mbo.get_value(u) << "\n"; } +static void display_project(vector const& defs) { + for (auto const& d : defs) { + std::cout << d << "\n"; + } +} + static void test5() { opt::model_based_opt mbo; unsigned x = mbo.add_var(rational(2)); @@ -231,13 +237,13 @@ static void test5() { add_ineq(mbo, z, 1, u, -1, 1, opt::t_le); unsigned vars[2] = { y, z }; - mbo.project(1, vars); + display_project(mbo.project(1, vars, true)); mbo.display(std::cout); - mbo.project(1, vars); + display_project(mbo.project(1, vars, true)); mbo.display(std::cout); - mbo.project(1, vars+1); + display_project(mbo.project(1, vars+1, true)); mbo.display(std::cout); vector rows; @@ -263,7 +269,7 @@ static void test6() { add_ineq(mbo, y, 1, w, -1, 1, opt::t_le); mbo.display(std::cout); - mbo.project(1, &y); + display_project(mbo.project(1, &y, true)); mbo.display(std::cout); } @@ -285,7 +291,7 @@ static void test7() { add_ineq(mbo, y, 1, w, -1, 1, opt::t_lt); mbo.display(std::cout); - mbo.project(1, &y); + display_project(mbo.project(1, &y, true)); mbo.display(std::cout); } @@ -306,7 +312,7 @@ static void test8() { add_ineq(mbo, y, 1, v, -1, 1, opt::t_le); mbo.display(std::cout); - mbo.project(1, &y); + display_project(mbo.project(1, &y, true)); mbo.display(std::cout); } @@ -327,7 +333,7 @@ static void test9() { add_ineq(mbo, y, 1, v, -1, 1, opt::t_le); mbo.display(std::cout); - mbo.project(1, &y); + display_project(mbo.project(1, &y, true)); mbo.display(std::cout); } @@ -348,17 +354,47 @@ static void test10() { add_ineq(mbo, y, 3, v, -6, 1, opt::t_le); mbo.display(std::cout); - mbo.project(1, &y); + display_project(mbo.project(1, &y, true)); mbo.display(std::cout); - mbo.project(1, &x0); + display_project(mbo.project(1, &x0, true)); mbo.display(std::cout); } +static void test11() { + { + opt::model_based_opt mbo; + unsigned s = mbo.add_var(rational(2), true); + unsigned a = mbo.add_var(rational(1), true); + unsigned t = mbo.add_var(rational(3), true); + + add_ineq(mbo, s, 1, a, -2, 0, opt::t_le); // s - 2a <= 0 + add_ineq(mbo, a, 2, t, -1, 0, opt::t_le); // 2a - t <= 0 + + mbo.display(std::cout); + display_project(mbo.project(1, &a, true)); + mbo.display(std::cout); + } + + { + opt::model_based_opt mbo; + unsigned s = mbo.add_var(rational(3), true); + unsigned a = mbo.add_var(rational(2), true); + unsigned t = mbo.add_var(rational(4), true); + + add_ineq(mbo, s, 1, a, -2, 0, opt::t_le); // s - 2a <= 0 + add_ineq(mbo, a, 2, t, -1, 0, opt::t_le); // 2a - t <= 0 + + mbo.display(std::cout); + display_project(mbo.project(1, &a, true)); + mbo.display(std::cout); + } + +} + // test with mix of upper and lower bounds void tst_model_based_opt() { test10(); - return; check_random_ineqs(); test1(); test2(); @@ -369,4 +405,5 @@ void tst_model_based_opt() { test7(); test8(); test9(); + test11(); } diff --git a/src/test/pdr.cpp b/src/test/pdr.cpp deleted file mode 100644 index 45d9eea7e..000000000 --- a/src/test/pdr.cpp +++ /dev/null @@ -1,128 +0,0 @@ - -/*++ -Copyright (c) 2015 Microsoft Corporation - ---*/ - -#include "muz/pdr/pdr_context.h" -#include "ast/reg_decl_plugins.h" - - -using namespace pdr; - -static expr_ref mk_state(expr_ref_vector const& states, random_gen& rand) { - expr_ref result(states.get_manager()); - result = states[rand(states.size())]; - return result; -} - - -struct test_model_search { - struct init_test { - init_test(func_decl_ref& fn) { - ast_manager& m = fn.get_manager(); - reg_decl_plugins(m); - fn = m.mk_const_decl(symbol("f"), m.mk_bool_sort()); - } - }; - - ast_manager m; - smt_params m_smt_params; - fixedpoint_params fp_params; - context ctx; - manager pm; - func_decl_ref fn; - init_test initt; - pred_transformer pt; - random_gen rand; - model_search search; - expr_ref_vector states; - - - test_model_search(): - ctx(m_smt_params, fp_params, m), - pm(m_smt_params, 10, m), - fn(m), - initt(fn), - pt(ctx, pm, fn), - rand(10), - search(true), - states(m) { - } - - void add_tree(model_node* parent, bool force_goal) { - unsigned level = parent->level(); - search.add_leaf(*parent); - expr_ref state(m); - if (level > 0 && (force_goal || parent->is_goal())) { - search.remove_goal(*parent); - - state = mk_state(states, rand); - add_tree(alloc(model_node, parent, state, pt, level-1), false); - - state = mk_state(states, rand); - add_tree(alloc(model_node, parent, state, pt, level-1), false); - parent->check_pre_closed(); - } - } - - bool mutate() { - model_node* leaf = search.next(); - if (!leaf) return false; - unsigned level = leaf->level(); - if (level == 0) { - if (rand(2) == 0) { - leaf->display(std::cout << "backtrack to grandparent\n", 1); - search.backtrack_level(false, *leaf->parent()); - } - else { - leaf->display(std::cout << "backtrack to parent\n", 1); - search.backtrack_level(false, *leaf); - } - } - else { - leaf->display(std::cout << "grow tree\n", 1); - add_tree(leaf, true); - } - return true; - } - - void init() { - std::cout << "pdr state-hash search tree\n"; - - expr_ref state(m); - func_decl_ref fn(m); - for (unsigned i = 0; i < 10; ++i) { - std::ostringstream strm; - strm << "s" << i; - state = m.mk_const(symbol(strm.str().c_str()), m.mk_bool_sort()); - fn = to_app(state)->get_decl(); - states.push_back(state); - } - state = states[0].get(); - unsigned level = 4; - for(unsigned n = 0; n < 100; ++n) { - state = mk_state(states, rand); - model_node* root = alloc(model_node, nullptr, state, pt, level); - search.set_root(root); - add_tree(root, false); - search.display(std::cout); - - while (true) { - search.well_formed(); - if (!mutate()) break; - search.display(std::cout); - } - search.reset(); - //++level; - } - search.reset(); - } - -}; - -void tst_pdr() { - test_model_search test; - - test.init(); -} diff --git a/src/test/qe_arith.cpp b/src/test/qe_arith.cpp index d18e0f717..031912c46 100644 --- a/src/test/qe_arith.cpp +++ b/src/test/qe_arith.cpp @@ -88,8 +88,7 @@ static void test(app* var, expr_ref& fml) { std::cout << "projected: " << mk_pp(pr, m) << "\n"; // projection is consistent with model. - expr_ref tmp(m); - VERIFY(md->eval(pr, tmp) && m.is_true(tmp)); + VERIFY(md->is_true(pr)); // projection implies E x. fml { diff --git a/src/test/solver_pool.cpp b/src/test/solver_pool.cpp new file mode 100644 index 000000000..4a2e533bf --- /dev/null +++ b/src/test/solver_pool.cpp @@ -0,0 +1,41 @@ +#include "ast/reg_decl_plugins.h" +#include "solver/solver_pool.h" +#include "smt/smt_solver.h" + +void tst_solver_pool() { + ast_manager m; + reg_decl_plugins(m); + params_ref p; + ref base = mk_smt_solver(m, p, symbol::null); + + expr_ref a(m.mk_const(symbol("a"), m.mk_bool_sort()), m); + expr_ref b(m.mk_const(symbol("b"), m.mk_bool_sort()), m); + expr_ref c(m.mk_const(symbol("c"), m.mk_bool_sort()), m); + expr_ref d(m.mk_const(symbol("d"), m.mk_bool_sort()), m); + expr_ref fml(m); + fml = m.mk_or(a, b); + base->assert_expr(fml); + + solver_pool pool(base.get(), 3); + + // base solver is cloned so any assertions + // added to base after mk_solver() are ignored. + ref s1 = pool.mk_solver(); + ref s2 = pool.mk_solver(); + ref s3 = pool.mk_solver(); + ref s4 = pool.mk_solver(); + + fml = m.mk_and(b, c); + s1->assert_expr(fml); + fml = m.mk_and(a, b); + s2->assert_expr(fml); + expr_ref_vector asms(m); + asms.push_back(m.mk_not(a)); + std::cout << base->check_sat(asms) << "\n"; + std::cout << s1->check_sat(asms) << "\n"; + std::cout << s2->check_sat(asms) << "\n"; + std::cout << s3->check_sat(asms) << "\n"; + std::cout << *s1; + std::cout << *s2; + std::cout << *base; +} diff --git a/src/util/mpz.cpp b/src/util/mpz.cpp index 39ea428a7..861a31cfb 100644 --- a/src/util/mpz.cpp +++ b/src/util/mpz.cpp @@ -45,43 +45,93 @@ Revision History: #define LEHMER_GCD #endif -template -static T gcd_core(T u, T v) { - if (u == 0) - return v; - if (v == 0) - return u; - int k; - - for (k = 0; ((u | v) & 1) == 0; ++k) { - u >>= 1; - v >>= 1; - } - - while ((u & 1) == 0) - u >>= 1; - +#include + +#if defined(__GNUC__) +#define _trailing_zeros32(X) __builtin_ctz(X) +#else +#define _trailing_zeros32(X) _tzcnt_u32(X) +#endif + +#if defined(_AMD64_) + #if defined(__GNUC__) + #define _trailing_zeros64(X) __builtin_ctzll(X) + #else + #define _trailing_zeros64(X) _tzcnt_u64(X) + #endif +#else +inline uint64_t _trailing_zeros64(uint64_t x) { + uint64_t r = 0; + for (; 0 == (x & 1) && r < 64; ++r, x >>= 1); + return r; +} +#endif + + +#define _bit_min(x, y) (y + ((x - y) & ((int)(x - y) >> 31))) +#define _bit_max(x, y) (x - ((x - y) & ((int)(x - y) >> 31))) + + +unsigned u_gcd(unsigned u, unsigned v) { + if (u == 0) return v; + if (v == 0) return u; + unsigned shift = _trailing_zeros32(u | v); + u >>= _trailing_zeros32(u); + if (u == 1 || v == 1) return 1 << shift; + if (u == v) return u << shift; do { - while ((v & 1) == 0) - v >>= 1; - - if (u < v) { - v -= u; - } - else { - T diff = u - v; - u = v; - v = diff; - } - v >>= 1; - } while (v != 0); - - return u << k; + v >>= _trailing_zeros32(v); +#if 1 + unsigned diff = u - v; + unsigned mdiff = diff & (unsigned)((int)diff >> 31); + u = v + mdiff; // min + v = diff - 2 * mdiff; // if v <= u: u - v, if v > u: v - u = u - v - 2 * (u - v) +#endif +#if 0 + unsigned t = _bit_max(u, v); + u = _bit_min(u, v); + v = t; + v -= u; +#endif +#if 0 + unsigned t = std::max(u, v); + u = std::min(u,v); + v = t; + v -= u; +#endif +#if 0 + if (u > v) std::swap(u, v); + v -= u; +#endif +#if 0 + unsigned d1 = u - v; + unsigned d2 = v - u; + unsigned md21 = d2 & (unsigned)((int)d1 >> 31); + unsigned md12 = d1 & (unsigned)((int)d2 >> 31); + u = _bit_min(u, v); + v = md12 | md21; +#endif + } + while (v != 0); + return u << shift; +} + +uint64_t u64_gcd(uint64_t u, uint64_t v) { + if (u == 0) return v; + if (v == 0) return u; + if (u == 1 || v == 1) return 1; + auto shift = _trailing_zeros64(u | v); + u >>= _trailing_zeros64(u); + do { + v >>= _trailing_zeros64(v); + if (u > v) std::swap(u, v); + v -= u; + } + while (v != 0); + return u << shift; } -unsigned u_gcd(unsigned u, unsigned v) { return gcd_core(u, v); } -uint64_t u64_gcd(uint64_t u, uint64_t v) { return gcd_core(u, v); } template mpz_manager::mpz_manager(): diff --git a/src/util/plugin_manager.h b/src/util/plugin_manager.h index c8908fd57..2c4223eeb 100644 --- a/src/util/plugin_manager.h +++ b/src/util/plugin_manager.h @@ -27,7 +27,12 @@ class plugin_manager { ptr_vector m_plugins; public: ~plugin_manager() { + reset(); + } + + void reset() { std::for_each(m_plugins.begin(), m_plugins.end(), delete_proc()); + release(); } /** diff --git a/src/util/util.h b/src/util/util.h index 6e7ee5ce5..12fd2fe3b 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -193,8 +193,10 @@ bool is_threaded(); #ifdef _MSC_VER #define DO_PRAGMA(x) __pragma(x) +#define PRAGMA_LOCK __pragma(omp critical (verbose_lock)) #else #define DO_PRAGMA(x) _Pragma(#x) +#define PRAGMA_LOCK _Pragma("omp critical (verbose_lock)") #endif #ifdef _NO_OMP_ @@ -202,7 +204,7 @@ bool is_threaded(); #else #define LOCK_CODE(CODE) \ { \ - DO_PRAGMA(omp critical (verbose_lock)) \ + PRAGMA_LOCK \ { \ CODE; \ } \ diff --git a/todo.txt b/todo.txt new file mode 100644 index 000000000..82038c3b4 --- /dev/null +++ b/todo.txt @@ -0,0 +1,9 @@ +- consolidate virtual-solver and pool solver + +- fixup mbp/mbi + +- API additions to expose functionality + +- Equality solver + +- Generalizer index \ No newline at end of file