From b269e6b35bba4fc09d3177024cde8424a7c18073 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:11:46 -0400 Subject: [PATCH 01/17] comments on proof_utils --- src/muz/base/proof_utils.cpp | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/muz/base/proof_utils.cpp b/src/muz/base/proof_utils.cpp index bbf9da0a4..3b2d50fdc 100644 --- a/src/muz/base/proof_utils.cpp +++ b/src/muz/base/proof_utils.cpp @@ -12,12 +12,19 @@ Copyright (c) 2015 Microsoft Corporation class reduce_hypotheses { typedef obj_hashtable expr_set; ast_manager& m; + // reference for any expression created by the tranformation expr_ref_vector m_refs; + // currently computed result obj_map m_cache; + // map conclusions to closed proofs that derive them obj_map m_units; + // currently active units ptr_vector m_units_trail; + // size of m_units_trail at the last push unsigned_vector m_limits; + // map from proofs to active hypotheses obj_map m_hypmap; + // refernce train for hypotheses sets ptr_vector m_hyprefs; ptr_vector m_literals; @@ -151,19 +158,33 @@ public: p = result; return; } + //SASSERT (p.get () == result); switch(p->get_decl_kind()) { case PR_HYPOTHESIS: + // replace result by m_units[m.get_fact (p)] if defined + // AG: This is the main step. Replace a hypothesis by a derivation of its consequence if (!m_units.find(m.get_fact(p), result)) { + // restore ther result back to p result = p.get(); } + // compute hypothesis of the result + // not clear what 'result' is at this point. + // probably the proof at the top of the call + // XXX not clear why this is re-computed each time + // XXX moreover, m_units are guaranteed to be closed! + // XXX so no hypotheses are needed for them add_hypotheses(result); break; case PR_LEMMA: { SASSERT(m.get_num_parents(p) == 1); tmp = m.get_parent(p, 0); + // eliminate hypothesis recursively in the proof of the lemma elim(tmp); expr_set* hyps = m_hypmap.find(tmp); expr_set* new_hyps = 0; + // XXX if the proof is correct, the hypotheses of the tmp + // XXX should be exactly those of the consequence of the lemma + // XXX but if this code actually eliminates hypotheses, the set might be a subset if (hyps) { new_hyps = alloc(expr_set, *hyps); } @@ -178,13 +199,19 @@ public: get_literals(fact); } + // go over all the literals in the consequence of the lemma for (unsigned i = 0; i < m_literals.size(); ++i) { expr* e = m_literals[i]; + // if the literal is not in hypothesis, skip it if (!in_hypotheses(e, hyps)) { m_literals[i] = m_literals.back(); m_literals.pop_back(); --i; } + // if the literal is in hypothesis remove it because + // it is not in hypothesis set of the lemma + // XXX but we assume that lemmas have empty hypothesis set. + // XXX eventually every element of new_hyps must be removed! else { SASSERT(new_hyps); expr_ref not_e = complement_lit(e); @@ -192,10 +219,13 @@ public: new_hyps->remove(not_e); } } + // killed all hypotheses, so can stop at the lemma since + // we have a closed pf of false if (m_literals.empty()) { result = tmp; } else { + // create a new lemma, but might be re-creating existing one expr_ref clause(m); if (m_literals.size() == 1) { clause = m_literals[0]; @@ -212,6 +242,7 @@ public: new_hyps = 0; } m_hypmap.insert(result, new_hyps); + // might push 0 into m_hyprefs. No reason for that m_hyprefs.push_back(new_hyps); TRACE("proof_utils", tout << "New lemma: " << mk_pp(m.get_fact(p), m) @@ -229,19 +260,27 @@ public: } case PR_UNIT_RESOLUTION: { proof_ref_vector parents(m); + // get the clause being resolved with parents.push_back(m.get_parent(p, 0)); + // save state push(); bool found_false = false; + // for every derivation of a unit literal for (unsigned i = 1; i < m.get_num_parents(p); ++i) { + // see if it derives false tmp = m.get_parent(p, i); elim(tmp); if (m.is_false(m.get_fact(tmp))) { + // if derived false, the whole pf is false and we can bail out result = tmp; found_false = true; break; } + // -- otherwise, the fact has not changed. nothing to simplify SASSERT(m.get_fact(tmp) == m.get_fact(m.get_parent(p, i))); parents.push_back(tmp); + // remember that we have this derivation while we have not poped the trail + // but only if the proof is closed (i.e., a real unit) if (is_closed(tmp) && !m_units.contains(m.get_fact(tmp))) { m_units.insert(m.get_fact(tmp), tmp); m_units_trail.push_back(m.get_fact(tmp)); @@ -251,10 +290,15 @@ public: pop(); break; } + // look at the clause being resolved with tmp = m.get_parent(p, 0); + // remember its fact expr* old_clause = m.get_fact(tmp); + // attempt to reduce its fact elim(tmp); + // update parents parents[0] = tmp; + // if the new fact is false, bail out expr* clause = m.get_fact(tmp); if (m.is_false(clause)) { m_refs.push_back(tmp); @@ -264,8 +308,10 @@ public: } // // case where clause is a literal in the old clause. + // i.e., reduce multi-literal clause to a unit // if (is_literal_in_clause(clause, old_clause)) { + // if the resulting literal was resolved, get a pf of false and bail out bool found = false; for (unsigned i = 1; !found && i < parents.size(); ++i) { if (m.is_complement(clause, m.get_fact(parents[i].get()))) { @@ -277,6 +323,7 @@ public: found = true; } } + // else if the resulting literal is not resolved, it is the new consequence if (!found) { result = parents[0].get(); } @@ -508,6 +555,11 @@ static void permute_unit_resolution(expr_ref_vector& refs, obj_map SASSERT(params[0].is_symbol()); family_id tid = m.mk_family_id(params[0].get_symbol()); SASSERT(tid != null_family_id); + // AG: This can break a theory lemma. In particular, for Farkas lemmas the coefficients + // AG: for the literals propagated from the unit resolution are missing. + // AG: Why is this a good thing to do? + // AG: This can lead to merging of the units with other terms in interpolation, + // AG: but without farkas coefficients this does not make sense prNew = m.mk_th_lemma(tid, m.get_fact(pr), premises.size(), premises.c_ptr(), num_params-1, params+1); } From ba6594b24176f5eb07bfabed4af90b6095ae5acd Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:14:22 -0400 Subject: [PATCH 02/17] extra smt params used by spacer --- src/smt/params/smt_params.cpp | 3 +++ src/smt/params/smt_params.h | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/smt/params/smt_params.cpp b/src/smt/params/smt_params.cpp index 453fb3e8e..8a8fac952 100644 --- a/src/smt/params/smt_params.cpp +++ b/src/smt/params/smt_params.cpp @@ -49,6 +49,9 @@ void smt_params::updt_local_params(params_ref const & _p) { else if (_p.get_bool("arith.least_error_pivot", false)) m_arith_pivot_strategy = ARITH_PIVOT_LEAST_ERROR; theory_array_params::updt_params(_p); + m_dump_benchmarks = false; + m_dump_min_time = 0.5; + m_dump_recheck = false; } void smt_params::updt_params(params_ref const & p) { diff --git a/src/smt/params/smt_params.h b/src/smt/params/smt_params.h index 5f93276a1..4539ebe58 100644 --- a/src/smt/params/smt_params.h +++ b/src/smt/params/smt_params.h @@ -217,6 +217,15 @@ struct smt_params : public preprocessor_params, bool m_dump_goal_as_smt; bool m_auto_config; + // ----------------------------------- + // + // Spacer hacking + // + // ----------------------------------- + bool m_dump_benchmarks; + double m_dump_min_time; + bool m_dump_recheck; + // ----------------------------------- // // Solver selection From 9f9dc5e19fb59fe912314d775106d37227447d33 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:15:37 -0400 Subject: [PATCH 03/17] increased verbosity level of smt_context --- src/smt/smt_context.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/smt_context.cpp b/src/smt/smt_context.cpp index ff64be64b..52b21dd80 100644 --- a/src/smt/smt_context.cpp +++ b/src/smt/smt_context.cpp @@ -3548,7 +3548,7 @@ namespace smt { return false; } if (cmr == quantifier_manager::UNKNOWN) { - IF_VERBOSE(1, verbose_stream() << "(smt.giveup quantifiers)\n";); + IF_VERBOSE(2, verbose_stream() << "(smt.giveup quantifiers)\n";); // giving up m_last_search_failure = QUANTIFIERS; status = l_undef; @@ -3558,7 +3558,7 @@ namespace smt { inc_limits(); if (status == l_true || !m_fparams.m_restart_adaptive || m_agility < m_fparams.m_restart_agility_threshold) { SASSERT(!inconsistent()); - IF_VERBOSE(1, verbose_stream() << "(smt.restarting :propagations " << m_stats.m_num_propagations + IF_VERBOSE(2, verbose_stream() << "(smt.restarting :propagations " << m_stats.m_num_propagations << " :decisions " << m_stats.m_num_decisions << " :conflicts " << m_stats.m_num_conflicts << " :restart " << m_restart_threshold; if (m_fparams.m_restart_strategy == RS_IN_OUT_GEOMETRIC) { From 5b9bf747873b42ece73636e8d0aae9c3dee012c3 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:33:41 -0400 Subject: [PATCH 04/17] Spacer engine for HORN logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The algorithms implemented in the engine are described in the following papers Anvesh Komuravelli, Nikolaj Bjørner, Arie Gurfinkel, Kenneth L. McMillan: Compositional Verification of Procedural Programs using Horn Clauses over Integers and Arrays. FMCAD 2015: 89-96 Nikolaj Bjørner, Arie Gurfinkel: Property Directed Polyhedral Abstraction. VMCAI 2015: 263-281 Anvesh Komuravelli, Arie Gurfinkel, Sagar Chaki: SMT-Based Model Checking for Recursive Programs. CAV 2014: 17-34 --- scripts/mk_project.py | 5 +- src/CMakeLists.txt | 1 + src/muz/base/dl_engine_base.h | 1 + src/muz/base/fixedpoint_params.pyg | 58 +- src/muz/fp/CMakeLists.txt | 1 + src/muz/fp/dl_register_engine.cpp | 3 + src/muz/spacer/CMakeLists.txt | 33 + src/muz/spacer/obj_equiv_class.h | 250 ++ src/muz/spacer/spacer_antiunify.cpp | 459 +++ src/muz/spacer/spacer_antiunify.h | 67 + src/muz/spacer/spacer_context.cpp | 3504 +++++++++++++++++ src/muz/spacer/spacer_context.h | 840 ++++ src/muz/spacer/spacer_dl_interface.cpp | 354 ++ src/muz/spacer/spacer_dl_interface.h | 86 + src/muz/spacer/spacer_farkas_learner.cpp | 440 +++ src/muz/spacer/spacer_farkas_learner.h | 66 + src/muz/spacer/spacer_generalizers.cpp | 294 ++ src/muz/spacer/spacer_generalizers.h | 99 + src/muz/spacer/spacer_itp_solver.cpp | 355 ++ src/muz/spacer/spacer_itp_solver.h | 177 + src/muz/spacer/spacer_legacy_frames.cpp | 170 + src/muz/spacer/spacer_legacy_frames.h | 47 + src/muz/spacer/spacer_legacy_mbp.cpp | 116 + src/muz/spacer/spacer_legacy_mev.cpp | 837 ++++ src/muz/spacer/spacer_legacy_mev.h | 117 + src/muz/spacer/spacer_manager.cpp | 386 ++ src/muz/spacer/spacer_manager.h | 345 ++ src/muz/spacer/spacer_marshal.cpp | 55 + src/muz/spacer/spacer_marshal.h | 29 + src/muz/spacer/spacer_matrix.cpp | 159 + src/muz/spacer/spacer_matrix.h | 46 + src/muz/spacer/spacer_mev_array.cpp | 217 + src/muz/spacer/spacer_mev_array.h | 52 + src/muz/spacer/spacer_min_cut.cpp | 289 ++ src/muz/spacer/spacer_min_cut.h | 52 + src/muz/spacer/spacer_notes.txt | 231 ++ src/muz/spacer/spacer_proof_utils.cpp | 332 ++ src/muz/spacer/spacer_proof_utils.h | 43 + src/muz/spacer/spacer_prop_solver.cpp | 298 ++ src/muz/spacer/spacer_prop_solver.h | 143 + src/muz/spacer/spacer_qe_project.cpp | 2333 +++++++++++ src/muz/spacer/spacer_qe_project.h | 49 + src/muz/spacer/spacer_smt_context_manager.cpp | 79 + src/muz/spacer/spacer_smt_context_manager.h | 68 + src/muz/spacer/spacer_sym_mux.cpp | 608 +++ src/muz/spacer/spacer_sym_mux.h | 256 ++ src/muz/spacer/spacer_unsat_core_learner.cpp | 360 ++ src/muz/spacer/spacer_unsat_core_learner.h | 107 + src/muz/spacer/spacer_unsat_core_plugin.cpp | 776 ++++ src/muz/spacer/spacer_unsat_core_plugin.h | 115 + src/muz/spacer/spacer_util.cpp | 1393 +++++++ src/muz/spacer/spacer_util.h | 180 + src/muz/spacer/spacer_virtual_solver.cpp | 519 +++ src/muz/spacer/spacer_virtual_solver.h | 153 + 54 files changed, 18050 insertions(+), 3 deletions(-) create mode 100644 src/muz/spacer/CMakeLists.txt create mode 100644 src/muz/spacer/obj_equiv_class.h create mode 100644 src/muz/spacer/spacer_antiunify.cpp create mode 100644 src/muz/spacer/spacer_antiunify.h create mode 100644 src/muz/spacer/spacer_context.cpp create mode 100644 src/muz/spacer/spacer_context.h create mode 100644 src/muz/spacer/spacer_dl_interface.cpp create mode 100644 src/muz/spacer/spacer_dl_interface.h create mode 100644 src/muz/spacer/spacer_farkas_learner.cpp create mode 100644 src/muz/spacer/spacer_farkas_learner.h create mode 100644 src/muz/spacer/spacer_generalizers.cpp create mode 100644 src/muz/spacer/spacer_generalizers.h create mode 100644 src/muz/spacer/spacer_itp_solver.cpp create mode 100644 src/muz/spacer/spacer_itp_solver.h create mode 100644 src/muz/spacer/spacer_legacy_frames.cpp create mode 100644 src/muz/spacer/spacer_legacy_frames.h create mode 100644 src/muz/spacer/spacer_legacy_mbp.cpp create mode 100644 src/muz/spacer/spacer_legacy_mev.cpp create mode 100644 src/muz/spacer/spacer_legacy_mev.h create mode 100644 src/muz/spacer/spacer_manager.cpp create mode 100644 src/muz/spacer/spacer_manager.h create mode 100644 src/muz/spacer/spacer_marshal.cpp create mode 100644 src/muz/spacer/spacer_marshal.h create mode 100644 src/muz/spacer/spacer_matrix.cpp create mode 100644 src/muz/spacer/spacer_matrix.h create mode 100644 src/muz/spacer/spacer_mev_array.cpp create mode 100644 src/muz/spacer/spacer_mev_array.h create mode 100644 src/muz/spacer/spacer_min_cut.cpp create mode 100644 src/muz/spacer/spacer_min_cut.h create mode 100644 src/muz/spacer/spacer_notes.txt create mode 100644 src/muz/spacer/spacer_proof_utils.cpp create mode 100644 src/muz/spacer/spacer_proof_utils.h create mode 100644 src/muz/spacer/spacer_prop_solver.cpp create mode 100644 src/muz/spacer/spacer_prop_solver.h create mode 100644 src/muz/spacer/spacer_qe_project.cpp create mode 100644 src/muz/spacer/spacer_qe_project.h create mode 100644 src/muz/spacer/spacer_smt_context_manager.cpp create mode 100644 src/muz/spacer/spacer_smt_context_manager.h create mode 100644 src/muz/spacer/spacer_sym_mux.cpp create mode 100644 src/muz/spacer/spacer_sym_mux.h create mode 100644 src/muz/spacer/spacer_unsat_core_learner.cpp create mode 100644 src/muz/spacer/spacer_unsat_core_learner.h create mode 100644 src/muz/spacer/spacer_unsat_core_plugin.cpp create mode 100644 src/muz/spacer/spacer_unsat_core_plugin.h create mode 100644 src/muz/spacer/spacer_util.cpp create mode 100644 src/muz/spacer/spacer_util.h create mode 100644 src/muz/spacer/spacer_virtual_solver.cpp create mode 100644 src/muz/spacer/spacer_virtual_solver.h diff --git a/scripts/mk_project.py b/scripts/mk_project.py index 8655346f2..b40205445 100644 --- a/scripts/mk_project.py +++ b/scripts/mk_project.py @@ -65,12 +65,13 @@ def init_project_def(): 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('duality_intf', ['muz', 'transforms', 'duality'], 'muz/duality') - add_lib('fp', ['muz', 'pdr', 'clp', 'tab', 'rel', 'bmc', 'duality_intf', 'ddnf'], 'muz/fp') + add_lib('fp', ['muz', 'pdr', 'clp', 'tab', 'rel', 'bmc', 'duality_intf', 'ddnf', 'spacer'], 'muz/fp') add_lib('nlsat_smt_tactic', ['nlsat_tactic', 'smt_tactic'], 'tactic/nlsat_smt') 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') @@ -79,7 +80,7 @@ def init_project_def(): add_lib('portfolio', ['smtlogic_tactics', 'sat_solver', 'ufbv_tactic', 'fpa_tactics', 'aig_tactic', 'fp', 'qe','sls_tactic', 'subpaving_tactic'], 'tactic/portfolio') add_lib('smtparser', ['portfolio'], 'parsers/smt') add_lib('opt', ['smt', 'smtlogic_tactics', 'sls_tactic', 'sat_solver'], 'opt') - 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_interp.h', 'z3_fpa.h'] + 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_interp.h', 'z3_fpa.h', 'z3_spacer.h'] add_lib('api', ['portfolio', 'smtparser', 'realclosure', 'interp', 'opt'], includes2install=['z3.h', 'z3_v1.h', 'z3_macros.h'] + API_files) add_exe('shell', ['api', 'sat', 'extra_cmds','opt'], exe_name='z3') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd440b34d..fe9fa2a82 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,6 +92,7 @@ add_subdirectory(muz/tab) add_subdirectory(muz/bmc) add_subdirectory(muz/ddnf) add_subdirectory(muz/duality) +add_subdirectory(muz/spacer) add_subdirectory(muz/fp) add_subdirectory(tactic/nlsat_smt) add_subdirectory(tactic/ufbv) diff --git a/src/muz/base/dl_engine_base.h b/src/muz/base/dl_engine_base.h index b2bb3dc63..380ae6e07 100644 --- a/src/muz/base/dl_engine_base.h +++ b/src/muz/base/dl_engine_base.h @@ -25,6 +25,7 @@ namespace datalog { enum DL_ENGINE { DATALOG_ENGINE, PDR_ENGINE, + SPACER_ENGINE, QPDR_ENGINE, BMC_ENGINE, QBMC_ENGINE, diff --git a/src/muz/base/fixedpoint_params.pyg b/src/muz/base/fixedpoint_params.pyg index 8e7d6a7cb..110f081b0 100644 --- a/src/muz/base/fixedpoint_params.pyg +++ b/src/muz/base/fixedpoint_params.pyg @@ -3,7 +3,7 @@ def_module_params('fixedpoint', export=True, params=(('timeout', UINT, UINT_MAX, 'set timeout'), ('engine', SYMBOL, 'auto-config', - 'Select: auto-config, datalog, duality, pdr, bmc'), + 'Select: auto-config, datalog, duality, pdr, bmc, spacer'), ('datalog.default_table', SYMBOL, 'sparse', 'default table implementation: sparse, hashtable, bitvector, interval'), ('datalog.default_relation', SYMBOL, 'pentagon', @@ -54,6 +54,8 @@ def_module_params('fixedpoint', "if true, finite_product_relation will attempt to avoid creating " + "inner relation with empty signature by putting in half of the " + "table columns, if it would have been empty otherwise"), + ('datalog.subsumption', BOOL, True, + "if true, removes/filters predicates with total transitions"), ('duality.full_expand', BOOL, False, 'Fully expand derivation trees'), ('duality.no_conj', BOOL, False, 'No forced covering (conjectures)'), ('duality.feasible_edges', BOOL, True, @@ -74,6 +76,8 @@ def_module_params('fixedpoint', ('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, @@ -138,13 +142,65 @@ def_module_params('fixedpoint', ('xform.slice', BOOL, True, "simplify clause set using slicing"), ('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, + "Rewrites arrays equalities and applies select over store"), + ('xform.instantiate_arrays', BOOL, False, + "Transforms P(a) into P(i, a[i] a)"), + ('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, + "Gives the number of quantifiers per array"), + ('xform.instantiate_arrays.slice_technique', SYMBOL, "no-slicing", + "=> GetId(i) = i, => GetId(i) = true"), ('xform.quantify_arrays', BOOL, False, "create quantified Horn clauses from clauses with arrays"), ('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 simplificaiton"), ('duality.enable_restarts', BOOL, False, 'DUALITY: enable restarts'), + ('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.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.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.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-deterministicly"), + ('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.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.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') )) diff --git a/src/muz/fp/CMakeLists.txt b/src/muz/fp/CMakeLists.txt index 4a7c4d018..0c5f5e915 100644 --- a/src/muz/fp/CMakeLists.txt +++ b/src/muz/fp/CMakeLists.txt @@ -12,6 +12,7 @@ z3_add_component(fp muz pdr rel + spacer tab TACTIC_HEADERS horn_tactic.h diff --git a/src/muz/fp/dl_register_engine.cpp b/src/muz/fp/dl_register_engine.cpp index 4d32cb543..b56a07a7c 100644 --- a/src/muz/fp/dl_register_engine.cpp +++ b/src/muz/fp/dl_register_engine.cpp @@ -24,6 +24,7 @@ Revision History: #include "muz/pdr/pdr_dl_interface.h" #include "muz/ddnf/ddnf.h" #include "muz/duality/duality_dl_interface.h" +#include "muz/spacer/spacer_dl_interface.h" namespace datalog { register_engine::register_engine(): m_ctx(0) {} @@ -33,6 +34,8 @@ namespace datalog { 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: return alloc(rel_context, *m_ctx); case BMC_ENGINE: diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt new file mode 100644 index 000000000..bc2f45b43 --- /dev/null +++ b/src/muz/spacer/CMakeLists.txt @@ -0,0 +1,33 @@ +z3_add_component(spacer + SOURCES + spacer_legacy_mev.cpp + spacer_legacy_frames.cpp + spacer_context.cpp + spacer_dl_interface.cpp + spacer_farkas_learner.cpp + spacer_generalizers.cpp + spacer_manager.cpp + spacer_marshal.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_legacy_mbp.cpp + spacer_proof_utils.cpp + spacer_unsat_core_learner.cpp + spacer_unsat_core_plugin.cpp + spacer_matrix.cpp + spacer_min_cut.cpp + spacer_antiunify.cpp + spacer_mev_array.cpp + spacer_qe_project.cpp + COMPONENT_DEPENDENCIES + arith_tactics + core_tactics + muz + qe + smt_tactic + transforms + ) diff --git a/src/muz/spacer/obj_equiv_class.h b/src/muz/spacer/obj_equiv_class.h new file mode 100644 index 000000000..b3184655c --- /dev/null +++ b/src/muz/spacer/obj_equiv_class.h @@ -0,0 +1,250 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + obj_equiv_class.h + +Abstract: + "Equivalence class structure" for objs. Uses a union_find structure internally. + Operations are : + -Declare a new equivalence class with a single element + -Merge two equivalence classes + -Retrieve whether two elements are in the same equivalence class + -Iterate on all the elements of the equivalence class of a given element + -Iterate on all equivalence classes (and then within them) + +Author: + + Julien Braine + +Revision History: + +*/ + +#ifndef OBJ_EQUIV_CLASS_H_ +#define OBJ_EQUIV_CLASS_H_ + +#include "union_find.h" +#include "ast_util.h" + +namespace spacer { +//All functions naturally add their parameters to the union_find class +template +class obj_equiv_class { + basic_union_find m_uf; + obj_map m_to_int; + ref_vector m_to_obj; + + unsigned add_elem_impl(OBJ*o) { + unsigned id = m_to_obj.size(); + m_to_int.insert(o, id); + m_to_obj.push_back(o); + return id; + } + unsigned add_if_not_there(OBJ*o) { + unsigned id; + if(!m_to_int.find(o, id)) { + id = add_elem_impl(o); + } + return id; + } + +public: + class iterator; + class equiv_iterator; + friend class iterator; + friend class equiv_iterator; + + obj_equiv_class(Manager& m) : m_to_obj(m) {} + + void add_elem(OBJ*o) { + SASSERT(!m_to_int.find(o)); + add_elem_impl(o); + } + + //Invalidates all iterators + void merge(OBJ* a, OBJ* b) { + unsigned v1 = add_if_not_there(a); + unsigned v2 = add_if_not_there(b); + unsigned tmp1 = m_uf.find(v1); + unsigned tmp2 = m_uf.find(v2); + m_uf.merge(tmp1, tmp2); + } + + void reset() { + m_uf.reset(); + m_to_int.reset(); + m_to_obj.reset(); + } + + bool are_equiv(OBJ*a, OBJ*b) { + unsigned id1 = add_if_not_there(a); + unsigned id2 = add_if_not_there(b); + return m_uf.find(id1) == m_uf.find(id2); + } + + class iterator { + friend class obj_equiv_class; + private : + const obj_equiv_class& m_ouf; + unsigned m_curr_id; + bool m_first; + iterator(const obj_equiv_class& uf, unsigned id, bool f) : + m_ouf(uf), m_curr_id(id), m_first(f) {} + public : + OBJ*operator*() {return m_ouf.m_to_obj[m_curr_id];} + + iterator& operator++() { + m_curr_id = m_ouf.m_uf.next(m_curr_id); + m_first = false; + return *this; + } + bool operator==(const iterator& o) { + SASSERT(&m_ouf == &o.m_ouf); + return m_first == o.m_first && m_curr_id == o.m_curr_id; + } + bool operator!=(const iterator& o) {return !(*this == o);} + }; + + iterator begin(OBJ*o) { + unsigned id = add_if_not_there(o); + return iterator(*this, id, true); + } + iterator end(OBJ*o) { + unsigned id = add_if_not_there(o); + return iterator(*this, id, false); + } + + class eq_class { + private : + iterator m_begin; + iterator m_end; + public : + eq_class(const iterator& a, const iterator& b) : m_begin(a), m_end(b) {} + iterator begin() {return m_begin;} + iterator end() {return m_end;} + }; + + class equiv_iterator { + friend class obj_equiv_class; + private : + const obj_equiv_class& m_ouf; + unsigned m_rootnb; + equiv_iterator(const obj_equiv_class& uf, unsigned nb) : + m_ouf(uf), m_rootnb(nb) { + while(m_rootnb != m_ouf.m_to_obj.size() && + m_ouf.m_uf.is_root(m_rootnb) != true) + { m_rootnb++; } + } + public : + eq_class operator*() { + return eq_class(iterator(m_ouf, m_rootnb, true), + iterator(m_ouf, m_rootnb, false)); + } + equiv_iterator& operator++() { + do { + m_rootnb++; + } while(m_rootnb != m_ouf.m_to_obj.size() && + m_ouf.m_uf.is_root(m_rootnb) != true); + return *this; + } + bool operator==(const equiv_iterator& o) { + SASSERT(&m_ouf == &o.m_ouf); + return m_rootnb == o.m_rootnb; + } + bool operator!=(const equiv_iterator& o) {return !(*this == o);} + }; + + equiv_iterator begin() {return equiv_iterator(*this, 0);} + equiv_iterator end() {return equiv_iterator(*this, m_to_obj.size());} +}; + +typedef obj_equiv_class expr_equiv_class; + + +/** + Factors input vector v into equivalence classes and the rest + */ +inline void factor_eqs(expr_ref_vector &v, expr_equiv_class &equiv) { + ast_manager &m = v.get_manager(); + arith_util arith(m); + expr *e1, *e2; + + flatten_and(v); + unsigned j = 0; + for (unsigned i = 0; i < v.size(); ++i) { + if (m.is_eq(v.get(i), e1, e2)) { + if (arith.is_zero(e1)) { + expr* t; + t = e1; e1 = e2; e2 = t; + } + + // y + -1*x == 0 + if (arith.is_zero(e2) && arith.is_add(e1) && + to_app(e1)->get_num_args() == 2) { + expr *a0, *a1, *x; + + a0 = to_app(e1)->get_arg(0); + a1 = to_app(e1)->get_arg(1); + + if (arith.is_times_minus_one(a1, x)) { + e1 = a0; + e2 = x; + } + else if (arith.is_times_minus_one(a0, x)) { + e1 = a1; + e2 = x; + } + } + equiv.merge(e1, e2); + } + else { + if (j < i) {v[j] = v.get(i);} + j++; + } + } + v.shrink(j); +} + +/** + * converts equivalence classes to equalities + */ +inline 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; + } + } + SASSERT(rep); + for (expr *elem : eq_class) { + if (rep != elem) {out.push_back (m.mk_eq (rep, elem));} + } + } +} + +/** + * expands equivalence classes to all derivable equalities + */ +inline bool equiv_to_expr_full(expr_equiv_class &equiv, expr_ref_vector &out) { + ast_manager &m = out.get_manager(); + bool dirty = false; + for (auto eq_class : equiv) { + for (auto a = eq_class.begin(), end = eq_class.end(); a != end; ++a) { + expr_equiv_class::iterator b(a); + for (++b; b != end; ++b) { + out.push_back(m.mk_eq(*a, *b)); + dirty = true; + } + } + } + return dirty; +} + +} + +#endif diff --git a/src/muz/spacer/spacer_antiunify.cpp b/src/muz/spacer/spacer_antiunify.cpp new file mode 100644 index 000000000..56fbbb8f0 --- /dev/null +++ b/src/muz/spacer/spacer_antiunify.cpp @@ -0,0 +1,459 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_antiunify.cpp + +Abstract: + + Antiunification utilities + +Author: + + Bernhard Gleiss + Arie Gurfinkel + +Revision History: + +--*/ + +#include"spacer_antiunify.h" +#include"ast.h" +#include"rewriter.h" +#include"rewriter_def.h" +#include"arith_decl_plugin.h" +#include"ast_util.h" +#include"expr_abstract.h" + +namespace spacer { + +// Abstracts numeric values by variables +struct var_abs_rewriter : public default_rewriter_cfg { + ast_manager &m; + arith_util m_util; + ast_mark m_seen; + ast_mark m_has_num; + unsigned m_var_index; + expr_ref_vector m_pinned; + obj_map& m_substitution; + ptr_vector m_stack; + + var_abs_rewriter (ast_manager &manager, obj_map& substitution, + unsigned k = 0) : + m(manager), m_util(m), m_var_index(k), + m_pinned(m), m_substitution(substitution) {} + + void reset(unsigned k = 0) { + m_pinned.reset(); + m_var_index = k; + } + + bool pre_visit(expr * t) { + bool r = (!m_seen.is_marked(t) || m_has_num.is_marked(t)); + // only unify if convex closure will not contain non-linear multiplication + if (m_util.is_mul(t)) + { + 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))) { + contains_const_child = true; + } + } + if (!contains_const_child) {r = false;} + } + 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); + return BR_FAILED; + } + } + } + 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_util.is_numeral(s)) { + t = m.mk_var(m_var_index++, m.get_sort(s)); + m_substitution.insert(t, s); + m_pinned.push_back(t); + m_has_num.mark(s, true); + m_seen.mark(t, true); + return true; + } + return false; + } + +}; + +/* +* 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; + + 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 +} + +/* 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); + + ptr_vector todo; + ptr_vector todo2; + todo.push_back(m_g); + todo2.push_back(t); + + ast_mark visited; + + arith_util util(m); + + 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); + } + } + else { + todo.pop_back(); + } + } + + m_g = generalization[m_g]; +} + + +class ncc_less_than_key +{ +public: + ncc_less_than_key(arith_util& util) : m_util(util) {} + + bool operator() (const expr*& e1, const expr*& e2) { + rational val1; + rational val2; + + if (m_util.is_numeral(e1, val1) && m_util.is_numeral(e2, val2)) + { + return val1 < val2; + } + else + { + SASSERT(false); + return false; + } + } + arith_util m_util; +}; + +/* + * if there is a single interval which exactly captures each of the + * substitutions, return the corresponding closure, otherwise do + * nothing + */ +bool naive_convex_closure::compute_closure(anti_unifier& au, ast_manager& m, + expr_ref& result) { + arith_util util(m); + + SASSERT(au.get_num_substitutions() > 0); + if (au.get_substitution(0).size() == 0) { + result = au.get_generalization(); + return true; + } + + // check that all substitutions have the same size + for (unsigned i=0, sz = au.get_num_substitutions(); i+1 < sz; ++i) { + if (au.get_substitution(i).size() != au.get_substitution(i+1).size()) { + return false; + } + } + + // for each substitution entry + bool is_first_key = true; + unsigned lower_bound; + unsigned upper_bound; + for (const auto& pair : au.get_substitution(0)) { + // construct vector + expr* key = &pair.get_key(); + vector entries; + + rational val; + for (unsigned i=0, sz = au.get_num_substitutions(); i < sz; ++i) + { + if (util.is_numeral(au.get_substitution(i)[key], val) && + val.is_unsigned()) { + entries.push_back(val.get_unsigned()); + } + else { + return false; + } + } + + // check whether vector represents interval + unsigned current_lower_bound; + unsigned current_upper_bound; + + // if vector represents interval + if (get_range(entries, current_lower_bound, current_upper_bound)) { + // if interval is the same as previous interval + if (is_first_key) { + is_first_key = false; + lower_bound = current_lower_bound; + upper_bound = current_upper_bound; + } + else { + if (current_lower_bound != lower_bound || + current_upper_bound != upper_bound) { + return false; + } + } + } + // otherwise we don't do a convex closure + else { + return false; + } + } + + // we finally know that we can express the substitutions using a + // single interval, so build the expression 1. construct const + expr_ref const_ref(m.mk_const(symbol("scti!0"), util.mk_int()), m); + + // 2. construct body with const + expr_ref lit1(util.mk_le(util.mk_int(lower_bound), const_ref), m); + expr_ref lit2(util.mk_le(const_ref, util.mk_int(upper_bound)), m); + expr_ref lit3(m); + substitute_vars_by_const(m, au.get_generalization(), const_ref, lit3); + + expr_ref_vector args(m); + args.push_back(lit1); + args.push_back(lit2); + args.push_back(lit3); + expr_ref body_with_consts = mk_and(args); + + // 3. replace const by var + ptr_vector vars; + vars.push_back(const_ref); + + expr_ref body(m); + expr_abstract(m, 0, vars.size(), (expr*const*)vars.c_ptr(), body_with_consts, body); + + // 4. introduce quantifier + ptr_vector sorts; + sorts.push_back(util.mk_int()); + svector names; + names.push_back(symbol("scti!0")); + + result = expr_ref(m.mk_exists(vars.size(), sorts.c_ptr(), names.c_ptr(), body),m); + + return true; +} + +bool naive_convex_closure::get_range(vector& v, + unsigned int& lower_bound, unsigned int& upper_bound) +{ + // sort substitutions + std::sort(v.begin(), v.end()); + + // check that numbers are consecutive + for (unsigned i=0; i+1 < v.size(); ++i) { + if (v[i] + 1 != v[i+1]) { + return false; + } + } + + SASSERT(v.size() > 0); + lower_bound = v[0]; + upper_bound = v.back(); + + return true; +} + +struct subs_rewriter_cfg : public default_rewriter_cfg { + ast_manager &m; + expr_ref m_c; + + subs_rewriter_cfg (ast_manager &manager, expr* c) : m(manager), m_c(c, m) {} + + bool reduce_var(var * t, expr_ref & result, proof_ref & result_pr) { + result = m_c; + result_pr = 0; + return true; + } +}; + +void naive_convex_closure::substitute_vars_by_const(ast_manager& m, expr* t, + expr* c, expr_ref& res) { + subs_rewriter_cfg subs_cfg(m, c); + rewriter_tpl subs_rw (m, false, subs_cfg); + subs_rw (t, res); +} + +} + +template class rewriter_tpl; +template class rewriter_tpl; diff --git a/src/muz/spacer/spacer_antiunify.h b/src/muz/spacer/spacer_antiunify.h new file mode 100644 index 000000000..690bc4678 --- /dev/null +++ b/src/muz/spacer/spacer_antiunify.h @@ -0,0 +1,67 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_antiunify.h + +Abstract: + + Antiunification utilities + +Author: + + Bernhard Gleiss + Arie Gurfinkel + +Revision History: + +--*/ + +#ifndef _SPACER_ANTIUNIFY_H_ +#define _SPACER_ANTIUNIFY_H_ + +#include "ast.h" + +namespace spacer { +class anti_unifier +{ +public: + anti_unifier(expr* t, ast_manager& m); + ~anti_unifier() {} + + 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 + expr_ref_vector m_pinned; + + expr_ref m_g; + + vector> m_substitutions; +}; + +class naive_convex_closure +{ +public: + static bool compute_closure(anti_unifier& au, ast_manager& m, + expr_ref& result); + +private: + static bool get_range(vector& v, unsigned& lower_bound, + unsigned& upper_bound); + static void substitute_vars_by_const(ast_manager& m, expr* t, expr* c, + expr_ref& res); +}; + +} +#endif diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp new file mode 100644 index 000000000..40709de7f --- /dev/null +++ b/src/muz/spacer/spacer_context.cpp @@ -0,0 +1,3504 @@ +/** +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_context.cpp + +Abstract: + + SPACER predicate transformers and search context. + +Author: + + Arie Gurfinkel + Anvesh Komuravelli + + Based on muz/pdr/pdr_context.cpp by Nikolaj Bjorner (nbjorner) + +Notes: + +--*/ + + +#include +#include + +#include "dl_util.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "var_subst.h" +#include "util.h" +#include "spacer_prop_solver.h" +#include "spacer_context.h" +#include "spacer_generalizers.h" +#include "for_each_expr.h" +#include "dl_rule_set.h" +#include "unit_subsumption_tactic.h" +#include "model_smt2_pp.h" +#include "dl_mk_rule_inliner.h" +#include "ast_smt2_pp.h" +#include "ast_ll_pp.h" +#include "ast_util.h" +#include "proof_checker.h" +#include "smt_value_sort.h" +#include "proof_utils.h" +#include "scoped_proof.h" +#include "spacer_qe_project.h" +#include "blast_term_ite_tactic.h" + +#include "timeit.h" +#include "luby.h" +#include "expr_safe_replace.h" +#include "expr_abstract.h" +#include "obj_equiv_class.h" + +namespace spacer { + +// ---------------- +// 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, 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) +{ + 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 ()))); +} + +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_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); + 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 pred_transformer::reset_statistics() +{ + m_solver.reset_statistics(); + //m_reachable.reset_statistics(); + m_stats.reset(); + m_initialize_watch.reset (); + m_must_reachable_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*)0, 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_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, NULL); + 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 NULL; +} + +reach_fact *pred_transformer::get_used_origin_reach_fact (model_evaluator_util& mev, + unsigned oidx) +{ + expr_ref b(m), v(m); + reach_fact *res = NULL; + + 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*)0); + 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*)0, 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 () ? NULL : 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, 0); + 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 = NULL; + } 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 (NULL); + m_solver.set_model (NULL); + + expr_ref_vector post(m), aux(m); + post.push_back (n.post ()); + lbool res = m_solver.check_assumptions (post, aux, 0, NULL, 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(0); + 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 (0); + 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(0); + } + 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); + } + 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::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) +{ + m_frames.inherit_frames (other.m_frames); +} + + +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(0), m_new_pob(false) { + SASSERT(m_body); + normalize(m_body, m_body); +} + +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);} + +lemma::lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl) : lemma(p) { + update_cube(p, cube); + set_level(lvl); +} + +void lemma::mk_expr_core() { + if (m_body) return; + + 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); + + if (!m_pob->is_ground() && has_zk_const(m_body)) { + app_ref_vector zks(m); + m_pob->get_skolems(zks); + zks.reverse(); + expr_abstract(m, 0, + zks.size(), (expr* const*)zks.c_ptr(), m_body, + m_body); + ptr_buffer sorts; + svector names; + for (unsigned i=0, sz=zks.size(); i < sz; ++i) { + sorts.push_back(get_sort(zks.get(i))); + names.push_back(zks.get(i)->get_decl()->get_name()); + } + 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(); + } + SASSERT(m_body); +} +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(); + } + else if (m_body) { + // no quantifiers for now + SASSERT(!is_quantifier(m_body)); + cube = m_body; + cube = ::push_not(cube); + } + flatten_and(cube, m_cube); + if (m_cube.empty()) { + m_cube.push_back(m.mk_true()); + } + else { + std::sort(m_cube.c_ptr(), m_cube.c_ptr() + m_cube.size(), ast_lt_proc()); + } + } + else { + UNREACHABLE(); + } +} +bool lemma::is_false() { + // a lemma is false if + // 1. it is defined by a cube, and the cube contains a single literal 'true' + // 2. it is defined by a body, and the body is a single literal false + // 3. it is defined by a pob, and the pob post is false + if (m_cube.size() == 1) {return m.is_true(m_cube.get(0));} + else if (m_body) {return m.is_false(m_body);} + else if (m_pob) {return m.is_true(m_pob->post());} + + return false; +} +expr* lemma::get_expr() { + mk_expr_core(); + return m_body; +} +expr_ref_vector const &lemma::get_cube() { + mk_cube_core(); + return m_cube; +} + +void lemma::update_cube (pob_ref const &p, expr_ref_vector &cube) { + SASSERT(m_pob); + SASSERT(m_pob.get() == p.get()); + m_cube.reset(); + m_body.reset(); + m_cube.append(cube); + if (m_cube.empty()) {m_cube.push_back(m.mk_true());} +} + +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); + out.push_back(inst); + } +} + +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";); + + 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()); + } + // 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()) { + TRACE("spacer", tout << "Already at a higher level: " + << pp_level(m_lemmas [i]->level()) << "\n";); + return false; + } + + // update level of the existing lemma + m_lemmas [i]->set_level(lem->level()); + // assert lemma in the solver + m_pt.add_lemma_core(m_lemmas[i]); + // 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) { + m_lemmas.swap (j, j+1); + } + + return true; + } + } + + // did not find, create new lemma + m_lemmas.push_back(lem); + m_sorted = false; + m_pt.add_lemma_core(lem); + return true; +} + + +void pred_transformer::frames::propagate_to_infinity (unsigned level) +{ + for (unsigned i = 0, sz = m_lemmas.size (); i < sz; ++i) + if (m_lemmas[i]->level() >= level && !is_infty_level(m_lemmas [i]->level())) { + m_lemmas [i]->set_level (infty_level ()); + m_pt.add_lemma_core (m_lemmas [i]); + m_sorted = false; + } +} + +void pred_transformer::frames::sort () +{ + if (m_sorted) { return; } + + m_sorted = true; + std::sort(m_lemmas.c_ptr(), m_lemmas.c_ptr() + m_lemmas.size (), m_lt); +} + +bool pred_transformer::frames::propagate_to_next_level (unsigned level) +{ + sort (); + bool all = true; + + + if (m_lemmas.empty()) { return all; } + + unsigned tgt_level = next_level (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;} + + + unsigned solver_level; + expr * curr = m_lemmas [i]->get_expr (); + if (m_pt.is_invariant(tgt_level, curr, solver_level)) { + m_lemmas [i]->set_level (solver_level); + m_pt.add_lemma_core (m_lemmas [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); + } + } else { + all = false; + ++i; + } + } + + return all; +} + +void pred_transformer::frames::simplify_formulas () +{ + // number of subsumed lemmas + unsigned num_sumbsumed = 0; + + // ensure that the lemmas are sorted + sort(); + ast_manager &m = m_pt.get_ast_manager (); + + 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 j = 0; + // for every frame + infinity frame + for (unsigned i = 0; i <= m_size; ++i) { + g->reset_all (); + // normalize level + unsigned level = i < m_size ? i : infty_level (); + + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref core(m); + goal_ref_buffer result; + + // simplify lemmas of the current level + // XXX lemmas of higher levels can be assumed in background + // XXX decide what to do with non-ground lemmas! + unsigned begin = j; + for (; j < lemmas_size && m_lemmas[j]->level() <= level; ++j) { + if (m_lemmas[j]->level() == level) { + g->assert_expr(m_lemmas[j]->get_expr()); + } + } + unsigned end = j; + + unsigned sz = end - begin; + // no lemmas at current level, move to next level + if (sz <= 0) {continue;} + + // exactly one lemma at current level, nothing to + // simplify. move to next level + if (sz == 1) { + new_lemmas.push_back(m_lemmas[begin]); + continue; + } + + // more than one lemma at current level. simplify. + (*simplifier)(g, result, mc, pc, core); + SASSERT(result.size () == 1); + goal *r = result[0]; + + // no simplification happened, copy all the lemmas + if (r->size () == sz) { + for (unsigned n = begin; n < end; ++n) { + new_lemmas.push_back (m_lemmas[n]); + } + } + // something got simplified, find out which lemmas remain + else { + num_sumbsumed += (sz - r->size()); + // For every expression in the result, copy corresponding + // lemma into new_lemmas + // XXX linear search. optimize if needed. + for (unsigned k = 0; k < r->size(); ++k) { + bool found = false; + for (unsigned n = begin; n < end; ++n) { + if (m_lemmas[n]->get_expr() == r->form(k)) { + new_lemmas.push_back(m_lemmas[n]); + found = true; + break; + } + } + if (!found) { + verbose_stream() << "Failed to find a lemma for: " + << mk_pp(r->form(k), m) << "\n"; + verbose_stream() << "Available lemmas are: "; + for (unsigned n = begin; n < end; ++n) { + verbose_stream() << n << ": " + << mk_pp(m_lemmas[n]->get_expr(), m) + << "\n"; + } + } + ENSURE(found); + SASSERT(found); + } + } + } + + SASSERT(new_lemmas.size() + num_sumbsumed == m_lemmas.size()); + ENSURE(new_lemmas.size() + num_sumbsumed == m_lemmas.size()); + if (new_lemmas.size() < m_lemmas.size()) { + m_lemmas.reset(); + m_lemmas.append(new_lemmas); + m_sorted = false; + sort(); + } +} + +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()) { + pob* n = alloc(pob, parent, m_pt, level, depth); + n->set_post(post, b); + return n; + } + + // create a new pob and set its post to normalize it + pob p(parent, m_pt, level, depth, false); + p.set_post(post, b); + + if (m_pobs.contains(p.post())) { + auto &buf = m_pobs[p.post()]; + for (unsigned i = 0, sz = buf.size(); i < sz; ++i) { + pob *f = buf.get(i); + if (f->parent() == parent) { + f->inherit(p); + return f; + } + } + } + + pob* n = alloc(pob, parent, m_pt, level, depth); + n->set_post(post, b); + m_pinned.push_back(n); + + if (m_pobs.contains(n->post())) { + m_pobs[n->post()].push_back(n); + } + else { + pob_buffer buf; + buf.push_back(n); + m_pobs.insert(n->post(), buf); + } + 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 NULL; } + 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 NULL; } + + // -- 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 NULL; + } + + + // 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 NULL; } + + 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 NULL; } + + 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 NULL; } + /// top queue element is above max level + if (m_obligations.top()->level() > m_max_level) { return NULL; } + /// 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 NULL; } + + /// 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) : + m_params(params), + m(m), + m_context(0), + m_pm(params.pdr_max_num_contexts(), m), + m_query_pred(m), + m_query(0), + 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()) +{} + +context::~context() +{ + reset_lemma_generalizers(); + reset(); +} + +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); + } + m_rels.reset(); + m_query = 0; + m_last_result = l_undef; + m_inductive_lvl = 0; +} + +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) { + 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)); + 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]); + } + } + 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_manager(), pred); + rels.insert(pred, pt); + } + } + } + // 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); + 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); ); + } + + // initialize reach facts + it = rels.begin (), end = rels.end (); + for (; it != end; ++it) + { it->m_value->init_reach_facts(); } +} + +void context::update_rules(datalog::rule_set& rules) +{ + decl2rel rels; + init_lemma_generalizers(rules); + init_rules(rules, rels); + decl2rel::iterator it = rels.begin(), end = rels.end(); + for (; it != end; ++it) { + pred_transformer* pt = 0; + if (m_rels.find(it->m_key, pt)) { + it->m_value->inherit_properties(*pt); + } + } + reset(); + it = rels.begin(), end = rels.end(); + for (; it != end; ++it) { + m_rels.insert(it->m_key, it->m_value); + } +} + +unsigned context::get_num_levels(func_decl* p) +{ + pred_transformer* pt = 0; + 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 = 0; + 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 = 0; + if (!m_rels.find(p, pt)) { + pt = alloc(pred_transformer, *this, get_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); +} + +void context::add_invariant (func_decl *p, expr *property) +{add_cover (infty_level(), p, property);} + +expr_ref context::get_reachable(func_decl *p) +{ + pred_transformer* pt = 0; + if (!m_rels.find(p, pt)) + { return expr_ref(m.mk_false(), m); } + return pt->get_reachable(); +} + +bool context::validate() +{ + if (!m_params.pdr_validate_result()) { return true; } + + std::stringstream msg; + + switch(m_last_result) { + case l_true: { + expr_ref cex(m); + cex = get_ground_sat_answer(); + 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; + 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 () + << "\n";); + for (unsigned i = 0; i < rules.size(); ++i) { + datalog::rule& r = *rules[i]; + + TRACE ("spacer", + get_datalog_context (). + get_rule_manager (). + display_smt2(r, tout) << "\n";); + + 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; + expr_free_vars fv; + fv (tmp); + 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 (); + 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(); + 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()); + return false; + } + } + } + TRACE ("spacer", tout << "Validation Succeeded\n";); + break; + } + default: + break; + } + return true; +} + + +void context::reset_lemma_generalizers() +{ + std::for_each(m_lemma_generalizers.begin(), m_lemma_generalizers.end(), + delete_proc()); + m_lemma_generalizers.reset(); +} + +void context::init_lemma_generalizers(datalog::rule_set& rules) +{ + reset_lemma_generalizers(); + m.toggle_proof_mode(PGM_FINE); + 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; + } + 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()) { + 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()) { + m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); + } + + if (m_params.spacer_use_array_eq_generalizer()) { + m_lemma_generalizers.push_back(alloc(lemma_array_eq_generalizer, *this)); + } + + if (get_params().spacer_lemma_sanity_check()) { + 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; + 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(unsigned from_lvl) +{ + m_last_result = l_undef; + try { + m_last_result = solve_core (from_lvl); + if (m_last_result == l_false) { + simplify_formulas(); + m_last_result = l_false; + 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); + // } + // } + } + VERIFY (validate ()); + } catch (unknown_exception) + {} + + if (m_last_result == l_true) { + m_stats.m_cex_depth = get_cex_depth (); + } + + if (m_params.print_statistics ()) { + statistics st; + collect_statistics (st); + st.display_smt2 (verbose_stream ()); + } + + return m_last_result; +} + + +void context::checkpoint() +{ + if (m.canceled ()) { + throw default_exception("spacer canceled"); + } +} + +unsigned context::get_cex_depth() +{ + if (m_last_result != l_true) { + IF_VERBOSE(1, + verbose_stream () + << "Trace unavailable when result is false\n";); + return 0; + } + + // treat the following as queues: read from left to right and insert at right + ptr_vector preds; + ptr_vector pts; + reach_fact_ref_vector facts; + + // temporary + reach_fact* fact; + datalog::rule const* r; + pred_transformer* pt; + + // get and discard query rule + fact = m_query->get_last_reach_fact (); + r = &fact->get_rule (); + + unsigned cex_depth = 0; + + // initialize queues + // assume that the query is only on a single predicate + // (i.e. disallow fancy queries for now) + facts.append (fact->get_justifications ()); + if (facts.size() != 1) { + // XXX AG: Escape if an assertion is about to fail + IF_VERBOSE(1, + verbose_stream () << + "Warning: counterexample is trivial or non-existent\n";); + return cex_depth; + } + SASSERT (facts.size () == 1); + m_query->find_predecessors (*r, preds); + SASSERT (preds.size () == 1); + pts.push_back (&(get_pred_transformer (preds[0]))); + + pts.push_back (NULL); // cex depth marker + + // bfs traversal of the query derivation tree + for (unsigned curr = 0; curr < pts.size (); curr++) { + // get current pt and fact + pt = pts.get (curr); + // check for depth marker + if (pt == NULL) { + ++cex_depth; + // insert new marker if there are pts at higher depth + if (curr + 1 < pts.size()) { pts.push_back(NULL); } + continue; + } + fact = facts.get (curr - cex_depth); // discount the number of markers + // get rule justifying the derivation of fact at pt + r = &fact->get_rule (); + TRACE ("spacer", + tout << "next rule: " << r->name ().str () << "\n"; + ); + // add child facts and pts + facts.append (fact->get_justifications ()); + pt->find_predecessors (*r, preds); + for (unsigned j = 0; j < preds.size (); j++) { + pts.push_back (&(get_pred_transformer (preds[j]))); + } + } + + return cex_depth; +} + +/** + \brief retrieve answer. +*/ + +void context::get_rules_along_trace(datalog::rule_ref_vector& rules) +{ + if (m_last_result != l_true) { + IF_VERBOSE(1, + verbose_stream () + << "Trace unavailable when result is false\n";); + return; + } + + // treat the following as queues: read from left to right and insert at right + ptr_vector preds; + ptr_vector pts; + reach_fact_ref_vector facts; + + // temporary + reach_fact* fact; + datalog::rule const* r; + pred_transformer* pt; + + // get query rule + fact = m_query->get_last_reach_fact (); + r = &fact->get_rule (); + rules.push_back (const_cast (r)); + TRACE ("spacer", + tout << "Initial rule: " << r->name().str() << "\n"; + ); + + // initialize queues + // assume that the query is only on a single predicate + // (i.e. disallow fancy queries for now) + facts.append (fact->get_justifications ()); + if (facts.size() != 1) { + // XXX AG: Escape if an assertion is about to fail + IF_VERBOSE(1, + verbose_stream () << + "Warning: counterexample is trivial or non-existent\n";); + return; + } + SASSERT (facts.size () == 1); + m_query->find_predecessors (*r, preds); + SASSERT (preds.size () == 1); + pts.push_back (&(get_pred_transformer (preds[0]))); + + // populate rules according to a preorder traversal of the query derivation tree + for (unsigned curr = 0; curr < pts.size (); curr++) { + // get current pt and fact + pt = pts.get (curr); + fact = facts.get (curr); + // get rule justifying the derivation of fact at pt + r = &fact->get_rule (); + rules.push_back (const_cast (r)); + TRACE ("spacer", + tout << "next rule: " << r->name ().str () << "\n"; + ); + // add child facts and pts + facts.append (fact->get_justifications ()); + pt->find_predecessors (*r, preds); + for (unsigned j = 0; j < preds.size (); j++) { + pts.push_back (&(get_pred_transformer (preds[j]))); + } + } +} + +model_ref context::get_model() +{ + model_ref model; + expr_ref_vector refs(m); + vector rs; + get_level_property(m_inductive_lvl, refs, rs); + inductive_property ex(m, const_cast(m_mc), rs); + ex.to_model (model); + return model; +} + +proof_ref context::get_proof() const +{ + return proof_ref (m); +} + +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); + } +} + +/** + \brief Retrieve satisfying assignment with explanation. +*/ +expr_ref context::mk_sat_answer() {return get_ground_sat_answer();} + + +expr_ref context::mk_unsat_answer() const +{ + 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(); +} + +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); + } + + // 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 + + // temporary + reach_fact *reach_fact; + pred_transformer* pt; + expr_ref cex_fact (m); + datalog::rule const* r; + + // get and discard query rule + reach_fact = m_query->get_last_reach_fact (); + r = &reach_fact->get_rule (); + + // initialize queues + reach_facts.append (reach_fact->get_justifications ()); + if (reach_facts.size() != 1) { + // XXX Escape if an assertion is about to fail + IF_VERBOSE(1, + verbose_stream () << + "Warning: counterexample is trivial or non-existent\n";); + return expr_ref(m.mk_true(), m); + } + m_query->find_predecessors (*r, preds); + SASSERT (preds.size () == 1); + pts.push_back (&(get_pred_transformer (preds[0]))); + cex_facts.push_back (m.mk_true ()); + + // XXX a hack to avoid assertion when query predicate is not nullary + if (preds[0]->get_arity () == 0) + { 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); + + // preorder traversal of the query derivation tree + for (unsigned curr = 0; curr < pts.size (); curr++) { + // pick next pt, fact, and cex_fact + pt = pts.get (curr); + reach_fact = reach_facts[curr]; + + cex_fact = cex_facts.get (curr); + + ptr_vector child_pts; + + // get justifying rule and child facts for the derivation of reach_fact at pt + r = &reach_fact->get_rule (); + const reach_fact_ref_vector &child_reach_facts = + reach_fact->get_justifications (); + // 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]))); + } + // update the queues + reach_facts.append (child_reach_facts); + pts.append (child_pts); + + // update cex and cex_facts by making a local sat check: + // check consistency of reach facts of children, rule body, and cex_fact + cex_ctx->push (); + cex_ctx->assert_expr (cex_fact); + unsigned u_tail_sz = r->get_uninterpreted_tail_size (); + 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); + cex_ctx->assert_expr (ofml); + } + cex_ctx->assert_expr (pt->transition ()); + cex_ctx->assert_expr (pt->rule2tag (r)); + lbool res = cex_ctx->check (); + CTRACE("cex", res == l_false, + tout << "Cex fact: " << mk_pp(cex_fact, m) << "\n"; + for (unsigned i = 0; i < u_tail_sz; i++) + tout << "Pre" << i << " " + << mk_pp(child_reach_facts[i]->get(), m) << "\n"; + tout << "Rule: "; + get_datalog_context().get_rule_manager().display_smt2(*r, tout) << "\n"; + ); + VERIFY (res == l_true); + 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); + 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); + } + 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); + 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 ())); + } + } + + TRACE ("spacer", + tout << "ground cex\n"; + for (unsigned i = 0; i < cex.size (); i++) { + tout << mk_pp (cex.get (i), m) << "\n"; + } + ); + + return expr_ref (m.mk_and (cex.size (), cex.c_ptr ()), m); +} + +///this is where everything starts +lbool context::solve_core (unsigned from_lvl) +{ + 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; } + + unsigned lvl = 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 (); + + for (unsigned i = 0; 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; } + + m_pob_queue.inc_level (); + lvl = m_pob_queue.max_level (); + m_stats.m_max_depth = std::max(m_stats.m_max_depth, lvl); + IF_VERBOSE(1,verbose_stream() << "Entering level "<< lvl << "\n";); + + STRACE("spacer.expand-add", tout << "\n* LEVEL " << lvl << "\n";); + + IF_VERBOSE(1, + if (m_params.print_statistics ()) { + statistics st; + collect_statistics (st); + }; + ); + } + // communicate failure to datalog::context + if (m_context) { m_context->set_status(datalog::BOUNDED); } + return l_undef; +} + + +// +bool context::check_reachability () +{ + scoped_watch _w_(m_reach_watch); + + timeit _timer (get_verbosity_level () >= 1, + "spacer::context::check_reachability", + verbose_stream ()); + + pob_ref last_reachable; + + if (get_params().spacer_reset_obligation_queue()) { m_pob_queue.reset(); } + + unsigned initial_size = m_stats.m_num_lemmas; + unsigned threshold = m_restart_initial_threshold; + unsigned luby_idx = 1; + + while (m_pob_queue.top()) { + pob_ref node; + checkpoint (); + + while (last_reachable) { + checkpoint (); + node = last_reachable; + last_reachable = NULL; + if (m_pob_queue.is_root(*node)) { return true; } + if (is_reachable (*node->parent())) { + last_reachable = node->parent (); + SASSERT(last_reachable->is_closed()); + last_reachable->close (); + } else if (!node->parent()->is_closed()) { + /* bump node->parent */ + node->parent ()->bump_weakness(); + } + } + + SASSERT (m_pob_queue.top ()); + // -- remove all closed nodes and updated all dirty nodes + // -- this is necessary because there is no easy way to + // -- remove nodes from the priority queue. + while (m_pob_queue.top ()->is_closed () || + m_pob_queue.top()->is_dirty()) { + pob_ref n = m_pob_queue.top (); + m_pob_queue.pop (); + if (n->is_closed()) { + IF_VERBOSE (1, + verbose_stream () << "Deleting closed node: " + << n->pt ().head ()->get_name () + << "(" << n->level () << ", " << n->depth () << ")" + << " " << n->post ()->get_id () << "\n";); + if (m_pob_queue.is_root(*n)) { return true; } + SASSERT (m_pob_queue.top ()); + } else if (n->is_dirty()) { + n->clean (); + // -- the node n might not be at the top after it is cleaned + m_pob_queue.push (*n); + } else + { UNREACHABLE(); } + } + + SASSERT (m_pob_queue.top ()); + + if (m_use_restarts && m_stats.m_num_lemmas - initial_size > threshold) { + luby_idx++; + m_stats.m_num_restarts++; + threshold = + static_cast(get_luby(luby_idx)) * m_restart_initial_threshold; + IF_VERBOSE (1, verbose_stream () + << "(restarting :lemmas " << m_stats.m_num_lemmas + << " :restart_threshold " << threshold + << ")\n";); + // -- clear obligation queue up to the root + while (!m_pob_queue.is_root(*m_pob_queue.top())) { m_pob_queue.pop(); } + initial_size = m_stats.m_num_lemmas; + } + + node = m_pob_queue.top (); + SASSERT (node->level () <= m_pob_queue.max_level ()); + switch (expand_node(*node)) { + case l_true: + SASSERT (m_pob_queue.top () == node.get ()); + m_pob_queue.pop (); + last_reachable = node; + last_reachable->close (); + if (m_pob_queue.is_root(*node)) { return true; } + break; + case l_false: + SASSERT (m_pob_queue.top () == node.get ()); + m_pob_queue.pop (); + + 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; } + break; + case l_undef: + // SASSERT (m_pob_queue.top () != node.get ()); + break; + } + } + + UNREACHABLE(); + return false; +} + +/// check whether node n is concretely reachable +bool context::is_reachable(pob &n) +{ + scoped_watch _w_(m_is_reach_watch); + // XXX hold a reference for n during this call. + // XXX Should convert is_reachable() to accept pob_ref as argument + pob_ref nref(&n); + + TRACE ("spacer", + tout << "is-reachable: " << n.pt().head()->get_name() + << " level: " << n.level() + << " depth: " << (n.depth () - m_pob_queue.min_depth ()) << "\n" + << mk_pp(n.post(), m) << "\n";); + + stopwatch watch; + IF_VERBOSE (1, verbose_stream () << "is-reachable: " << n.pt ().head ()->get_name () + << " (" << n.level () << ", " + << (n.depth () - m_pob_queue.min_depth ()) << ") " + << (n.use_farkas_generalizer () ? "FAR " : "SUB ") + << n.post ()->get_id (); + verbose_stream().flush (); + watch.start ();); + + // used in case n is unreachable + unsigned uses_level = infty_level (); + model_ref model; + + // used in case n is reachable + bool is_concrete; + const datalog::rule * r = NULL; + // denotes which predecessor's (along r) reach facts are used + vector reach_pred_used; + unsigned num_reuse_reach = 0; + + unsigned saved = n.level (); + n.m_level = infty_level (); + lbool res = n.pt().is_reachable(n, NULL, &model, + uses_level, is_concrete, r, + reach_pred_used, num_reuse_reach); + n.m_level = saved; + + if (res != l_true || !is_concrete) { + IF_VERBOSE(1, verbose_stream () << " F " + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + return false; + } + 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 ()); + } + + // if n has a derivation, create a new child and report l_undef + // otherwise if n has no derivation or no new children, report l_true + pob *next = NULL; + scoped_ptr deriv; + if (n.has_derivation()) {deriv = n.detach_derivation();} + + // -- close n, it is reachable + // -- don't worry about removing n from the obligation queue + n.close (); + + if (deriv) { + next = deriv->create_next_child (); + if (next) { + SASSERT(!next->is_closed()); + // 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); + } + } + + // either deriv was a nullptr or it was moved into next + SASSERT(!next || !deriv); + + + IF_VERBOSE(1, verbose_stream () << (next ? " X " : " T ") + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + + // recurse on the new proof obligation + return next ? is_reachable(*next) : true; +} + +//this processes a goal and creates sub-goal +lbool context::expand_node(pob& n) +{ + TRACE ("spacer", + tout << "expand-node: " << n.pt().head()->get_name() + << " level: " << n.level() + << " depth: " << (n.depth () - m_pob_queue.min_depth ()) << "\n" + << mk_pp(n.post(), m) << "\n";); + + STRACE ("spacer.expand-add", + tout << "expand-node: " << 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() + << " level: " << n.level() + << " depth: " << (n.depth () - m_pob_queue.min_depth ()) << "\n" + << mk_pp(n.post(), m) << "\n";); + + stopwatch watch; + IF_VERBOSE (1, verbose_stream () << "expand: " << n.pt ().head ()->get_name () + << " (" << n.level () << ", " + << (n.depth () - m_pob_queue.min_depth ()) << ") " + << (n.use_farkas_generalizer () ? "FAR " : "SUB ") + << " w(" << n.weakness() << ") " + << n.post ()->get_id (); + verbose_stream().flush (); + watch.start ();); + + // used in case n is unreachable + unsigned uses_level = infty_level (); + expr_ref_vector cube(m); + model_ref model; + + // used in case n is reachable + bool is_concrete; + const datalog::rule * r = NULL; + // denotes which predecessor's (along r) reach facts are used + vector reach_pred_used; + unsigned num_reuse_reach = 0; + + + if (get_params().pdr_flexible_trace() && 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";); + + 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); + + lbool res = n.pt ().is_reachable (n, &cube, &model, uses_level, is_concrete, r, + reach_pred_used, num_reuse_reach); + checkpoint (); + IF_VERBOSE (1, verbose_stream () << "." << std::flush;); + switch (res) { + //reachable but don't know if this is purely using UA + case l_true: { + // 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); + checkpoint (); + n.pt ().add_reach_fact (rf.get ()); + checkpoint (); + } + + // if n has a derivation, create a new child and report l_undef + // otherwise if n has no derivation or no new children, report l_true + pob *next = NULL; + scoped_ptr deriv; + if (n.has_derivation()) {deriv = n.detach_derivation();} + + // -- close n, it is reachable + // -- don't worry about removing n from the obligation queue + n.close (); + + if (deriv) { + next = deriv->create_next_child (); + checkpoint (); + if (next) { + // 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); + } + } + + + IF_VERBOSE(1, verbose_stream () << (next ? " X " : " T ") + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + return next ? l_undef : l_true; + } + + // create a child of n + VERIFY(create_children (n, *r, mev, reach_pred_used)); + IF_VERBOSE(1, verbose_stream () << " U " + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + return l_undef; + + } + // n is unreachable, create new summary facts + case l_false: { + timeit _timer (is_trace_enabled("spacer_timeit"), + "spacer::expand_node::false", + verbose_stream ()); + + // -- only update expanded level when new lemmas are generated at it. + if (n.level() < m_expanded_lvl) { m_expanded_lvl = n.level(); } + + TRACE("spacer", tout << "cube:\n"; + for (unsigned j = 0; j < cube.size(); ++j) + tout << mk_pp(cube[j].get(), m) << "\n";); + + + pob_ref nref(&n); + // -- create lemma from a pob and last unsat core + lemma_ref lemma = alloc(class lemma, pob_ref(&n), cube, uses_level); + + // -- run all lemma generalizers + for (unsigned i = 0; + // -- only generalize if lemma was constructed using farkas + n.use_farkas_generalizer () && !lemma->is_false() && + i < m_lemma_generalizers.size(); ++i) { + checkpoint (); + (*m_lemma_generalizers[i])(lemma); + } + + TRACE("spacer", tout << "invariant state: " + << (is_infty_level(lemma->level())?"(inductive)":"") + << mk_pp(lemma->get_expr(), m) << "\n";); + + bool v = n.pt().add_lemma (lemma.get()); + 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()) { + n.new_post (mk_and(lemma->get_cube())); + n.set_farkas_generalizer (false); + } + CASSERT("spacer", n.level() == 0 || check_invariant(n.level()-1)); + + + IF_VERBOSE(1, verbose_stream () << " F " + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + + return l_false; + } + case l_undef: + // something went wrong + if (n.weakness() < 100 /* MAX_WEAKENSS */) { + bool has_new_child = false; + SASSERT(m_weak_abs); + m_stats.m_expand_node_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); + } + IF_VERBOSE(1, verbose_stream() << " UNDEF " + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); + if (has_new_child) { return l_undef; } + + // -- failed to create a child, bump weakness and repeat + // -- the recursion is bounded by the levels of weakness supported + n.bump_weakness(); + return expand_node(n); + } + TRACE("spacer", tout << "unknown state: " + << mk_pp(m_pm.mk_and(cube), m) << "\n";); + throw unknown_exception(); + } + UNREACHABLE(); + 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). +// + +bool context::propagate(unsigned min_prop_lvl, + unsigned max_prop_lvl, unsigned full_prop_lvl) +{ + scoped_watch _w_(m_propagate_watch); + + if (min_prop_lvl == infty_level()) { return false; } + + timeit _timer (get_verbosity_level() >= 1, + "spacer::context::propagate", + verbose_stream ()); + + if (full_prop_lvl < max_prop_lvl) { full_prop_lvl = max_prop_lvl; } + + if (m_params.pdr_simplify_formulas_pre()) { + simplify_formulas(); + } + IF_VERBOSE (1, verbose_stream () << "Propagating: " << std::flush;); + + for (unsigned lvl = min_prop_lvl; lvl <= full_prop_lvl; lvl++) { + IF_VERBOSE (1, + if (lvl > max_prop_lvl && lvl == max_prop_lvl + 1) + verbose_stream () << " ! "; + verbose_stream () << lvl << " " << std::flush;); + + checkpoint(); + CTRACE ("spacer", lvl > max_prop_lvl && lvl == max_prop_lvl + 1, + tout << "In full propagation\n";); + + 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("spacer", check_invariant(lvl)); + + if (all_propagated) { + for (it = m_rels.begin(); it != end; ++it) { + checkpoint (); + pred_transformer& r = *it->m_value; + r.propagate_to_infinity (lvl); + } + if (lvl <= max_prop_lvl) { + m_inductive_lvl = lvl; + IF_VERBOSE(1, verbose_stream () << "\n";); + return true; + } + break; + } + + if (all_propagated && lvl == max_prop_lvl) { + m_inductive_lvl = lvl; + return true; + } else if (all_propagated && lvl > max_prop_lvl) { break; } + } + if (m_params.pdr_simplify_formulas_post()) { + simplify_formulas(); + } + + IF_VERBOSE(1, verbose_stream () << "\n";); + return false; +} + +reach_fact *context::mk_reach_fact (pob& n, model_evaluator_util &mev, + const datalog::rule& r) +{ + timeit _timer1 (is_trace_enabled("spacer_timeit"), + "mk_reach_fact", + 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); + + expr_ref_vector path_cons (m); + path_cons.push_back (pt.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); + // 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); + child_reach_facts.push_back (kid); + m_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))); } + + 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))); } + } + // collect aux vars to eliminate + ptr_vector& aux_vars = pt.get_aux_vars (r); + bool elim_aux = get_params ().spacer_elim_aux (); + if (elim_aux) { vars.append(aux_vars.size(), aux_vars.c_ptr()); } + + res = m_pm.mk_and (path_cons); + + // -- pick an implicant from the path condition + if (get_params().spacer_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); + } + + + TRACE ("spacer", + tout << "Reach fact, before QE:\n"; + tout << mk_pp (res, m) << "\n"; + tout << "Vars:\n"; + for (unsigned i = 0; i < vars.size(); ++i) { + tout << mk_pp(vars.get (i), m) << "\n"; + } + ); + + { + timeit _timer1 (is_trace_enabled("spacer_timeit"), + "mk_reach_fact::qe_project", + verbose_stream ()); + qe_project (m, vars, res, mev.get_model (), false, m_use_native_mbp); + } + + + TRACE ("spacer", + tout << "Reach fact, after QE project:\n"; + tout << mk_pp (res, m) << "\n"; + tout << "Vars:\n"; + for (unsigned i = 0; i < vars.size(); ++i) { + tout << mk_pp(vars.get (i), m) << "\n"; + } + ); + + SASSERT (vars.empty ()); + + m_stats.m_num_reach_queries++; + ptr_vector empty; + reach_fact *f = alloc(reach_fact, m, r, res, elim_aux ? empty : aux_vars); + for (unsigned i = 0, sz = child_reach_facts.size (); i < sz; ++i) + { f->add_justification(child_reach_facts.get(i)); } + return f; +} + + +/** + \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) +{ + + 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); + tout << "\n"; + tout << "Transition:\n" << mk_pp(T, m) << "\n"; + tout << "Phi:\n" << mk_pp(phi, 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); + + compute_implicant_literals (mev, forms, Phi); + + //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()); + + 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); + //qe::reduce_array_selects (*mev.get_model (), phi1); + SASSERT (!m_ground_cti || 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"; + ); + + // 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); + // } + + + 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; } + + pred_transformer &pt = get_pred_transformer (preds [j]); + + const ptr_vector *aux = NULL; + 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); + } + + // create post for the first child and add to queue + pob* kid = deriv->create_first_child (mev); + + // -- failed to create derivation, cleanup and bail out + if (!kid) { + dealloc(deriv); + return false; + } + SASSERT (kid); + kid->set_derivation (deriv); + + // Optionally disable derivation optimization + if (!get_params().spacer_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))) + { kid->reset_derivation(); } + + m_pob_queue.push (*kid); + m_stats.m_num_queries++; + return true; +} + + + + +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("SPACER num queries", m_stats.m_num_queries); + st.update("SPACER num reach queries", m_stats.m_num_reach_queries); + st.update("SPACER num reuse reach facts", m_stats.m_num_reuse_reach); + st.update("SPACER max query lvl", m_stats.m_max_query_lvl); + st.update("SPACER max depth", m_stats.m_max_depth); + st.update("SPACER inductive level", m_inductive_lvl); + st.update("SPACER cex depth", m_stats.m_cex_depth); + st.update("SPACER expand node undef", m_stats.m_expand_node_undef); + st.update("SPACER num lemmas", m_stats.m_num_lemmas); + st.update("SPACER restarts", m_stats.m_num_restarts); + + st.update ("time.spacer.init_rules", m_init_rules_watch.get_seconds ()); + st.update ("time.spacer.solve", m_solve_watch.get_seconds ()); + st.update ("time.spacer.solve.propagate", m_propagate_watch.get_seconds ()); + st.update ("time.spacer.solve.reach", m_reach_watch.get_seconds ()); + st.update ("time.spacer.solve.reach.is-reach", m_is_reach_watch.get_seconds ()); + st.update ("time.spacer.solve.reach.children", + m_create_children_watch.get_seconds ()); + m_pm.collect_statistics(st); + + 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_stats.reset(); + m_pm.reset_statistics(); + + for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) { + m_lemma_generalizers[i]->reset_statistics(); + } + + m_init_rules_watch.reset (); + m_solve_watch.reset (); + m_propagate_watch.reset (); + m_reach_watch.reset (); + m_is_reach_watch.reset (); + m_create_children_watch.reset (); +} + +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_pm.fparams2()); + 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("spacer", tout << "Check invariant level: " << lvl << " " << result << "\n" << mk_pp(fml, m) << "\n";); + return result == l_false; +} + +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); + + if (m.is_true(c)) { continue; } + + // replace local constants by bound variables. + expr_ref_vector args(m); + for (unsigned i = 0; i < r.sig_size(); ++i) + { args.push_back(m.mk_const(m_pm.o2n(r.sig(i), 0))); } + + expr_ref pred(m); + pred = m.mk_app(r.head (), r.sig_size(), args.c_ptr()); + + constraints.push_back(m.mk_implies(pred, c)); + } + + if (constraints.empty()) { return expr_ref(m.mk_true(), m); } + return m_pm.mk_and (constraints); +} + +void context::add_constraints (unsigned level, expr_ref c) +{ + if (!c.get()) { 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 = 0; + if (m_rels.find (to_app (e1)->get_decl (), r)) + { r->add_lemma(e2, level); } + } + } +} + +inline bool pob_lt::operator() (const pob *pn1, const pob *pn2) const +{ + SASSERT (pn1); + SASSERT (pn2); + const pob& n1 = *pn1; + const pob& n2 = *pn2; + + if (n1.level() != n2.level()) { return n1.level() < n2.level(); } + + if (n1.depth() != n2.depth()) { return n1.depth() < n2.depth(); } + + // -- a more deterministic order of proof obligations in a queue + // if (!n1.get_context ().get_params ().spacer_nondet_tie_break ()) + { + const expr* p1 = n1.post (); + const expr* p2 = n2.post (); + ast_manager &m = n1.get_ast_manager (); + + + // -- order by size. Less conjunctions is a proxy for + // -- generality. Currently, this takes precedence over + // -- predicates which might not be the best choice + unsigned sz1 = 1; + unsigned sz2 = 1; + + if (m.is_and(p1)) { sz1 = to_app(p1)->get_num_args(); } + if (m.is_and(p2)) { sz2 = to_app(p2)->get_num_args(); } + if (sz1 != sz2) { return sz1 < sz2; } + + // -- when all else fails, order by identifiers of the + // -- expressions. This roughly means that expressions created + // -- earlier are preferred. Note that variables in post are + // -- based on names of the predicates. Hence this guarantees an + // -- order over predicates as well. That is, two expressions + // -- are equal if and only if they correspond to the same proof + // -- obligation of the same predicate. + if (p1->get_id() != p2->get_id()) { return p1->get_id() < p2->get_id(); } + + if (n1.pt().head()->get_id() == n2.pt().head()->get_id()) { + IF_VERBOSE (1, + verbose_stream () + << "dup: " << n1.pt ().head ()->get_name () + << "(" << n1.level () << ", " << n1.depth () << ") " + << p1->get_id () << "\n"; + //<< " p1: " << mk_pp (const_cast(p1), m) << "\n" + ); + } + + // XXX see comment below on identical nodes + // SASSERT (n1.pt ().head ()->get_id () != n2.pt ().head ()->get_id ()); + // -- if expression comparison fails, compare by predicate id + if (n1.pt().head ()->get_id () != n2.pt ().head ()->get_id ()) + { return n1.pt().head()->get_id() < n2.pt().head()->get_id(); } + + /** 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 + * the same proof obligation multiple times */ + return &n1 < &n2; + } + // else + // return &n1 < &n2; +} + + + +} diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h new file mode 100644 index 000000000..f27ece97c --- /dev/null +++ b/src/muz/spacer/spacer_context.h @@ -0,0 +1,840 @@ +/**++ +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_context.h + +Abstract: + + SPACER predicate transformers and search context. + +Author: + + Arie Gurfinkel + Anvesh Komuravelli + + Based on muz/pdr/pdr_context.h by Nikolaj Bjorner (nbjorner) + +Notes: + +--*/ + +#ifndef _SPACER_CONTEXT_H_ +#define _SPACER_CONTEXT_H_ + +#ifdef _CYGWIN +#undef min +#undef max +#endif +#include +#include "spacer_manager.h" +#include "spacer_prop_solver.h" +#include "fixedpoint_params.hpp" + +namespace datalog { + class rule_set; + class context; +}; + +namespace spacer { + +class pred_transformer; +class derivation; +class pob_queue; +class context; + +typedef obj_map rule2inst; +typedef obj_map decl2rel; + +class pob; +typedef ref pob_ref; +typedef sref_vector pob_ref_vector; + +class reach_fact; +typedef ref reach_fact_ref; +typedef sref_vector reach_fact_ref_vector; + +class reach_fact { + unsigned m_ref_count; + + expr_ref m_fact; + ptr_vector m_aux_vars; + + const datalog::rule &m_rule; + reach_fact_ref_vector m_justification; + + bool m_init; + +public: + reach_fact (ast_manager &m, const datalog::rule &rule, + 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) {} + 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) {} + + bool is_init () {return m_init;} + const datalog::rule& get_rule () {return m_rule;} + + void add_justification (reach_fact *f) {m_justification.push_back (f);} + const reach_fact_ref_vector& get_justifications () {return m_justification;} + + expr *get () {return m_fact.get ();} + const ptr_vector &aux_vars () {return m_aux_vars;} + + void inc_ref () {++m_ref_count;} + void dec_ref () + { + SASSERT (m_ref_count > 0); + --m_ref_count; + if(m_ref_count == 0) { dealloc(this); } + } +}; + + +class lemma; +typedef ref lemma_ref; +typedef sref_vector lemma_ref_vector; + +typedef pob pob; + +// a lemma +class lemma { + unsigned m_ref_count; + + ast_manager &m; + expr_ref m_body; + expr_ref_vector m_cube; + app_ref_vector m_bindings; + unsigned m_lvl; + pob_ref m_pob; + bool m_new_pob; + + void mk_expr_core(); + void mk_cube_core(); +public: + lemma(ast_manager &manager, expr * fml, unsigned lvl); + lemma(pob_ref const &p); + lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl); + lemma(const lemma &other) = delete; + + ast_manager &get_ast_manager() {return m;} + expr *get_expr(); + bool is_false(); + expr_ref_vector const &get_cube(); + void update_cube(pob_ref const &p, expr_ref_vector &cube); + + bool has_pob() {return m_pob;} + pob_ref &get_pob() {return m_pob;} + + unsigned level () const {return m_lvl;} + void set_level (unsigned lvl) {m_lvl = lvl;} + app_ref_vector& get_bindings() {return m_bindings;} + void add_binding(app_ref_vector const &binding) {m_bindings.append(binding);} + 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); } + } +}; + +struct lemma_lt_proc : public std::binary_function { + bool operator() (lemma *a, lemma *b) { + return (a->level () < b->level ()) || + (a->level () == b->level () && + ast_lt_proc() (a->get_expr (), b->get_expr ())); + } +}; + + + +// +// 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; + unsigned m_num_invariants; + stats() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } + }; + + /// manager of the lemmas in all the frames +#include "spacer_legacy_frames.h" + class frames { + private: + pred_transformer &m_pt; + lemma_ref_vector m_lemmas; + unsigned m_size; + + bool m_sorted; + lemma_lt_proc m_lt; + + void sort (); + + public: + frames (pred_transformer &pt) : m_pt (pt), m_size(0), m_sorted (true) {} + ~frames() {} + void simplify_formulas (); + + pred_transformer& pt () {return m_pt;} + + + 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_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()); + } + } + + + 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()); + } + m_sorted = false; + } + + bool add_lemma (lemma *lem); + void propagate_to_infinity (unsigned level); + bool propagate_to_next_level (unsigned level); + + + }; + + /** + manager of proof-obligations (pobs) + */ + class pobs { + typedef ptr_buffer pob_buffer; + typedef obj_map expr2pob_buffer; + + pred_transformer &m_pt; + + expr2pob_buffer m_pobs; + pob_ref_vector m_pinned; + public: + pobs(pred_transformer &pt) : m_pt(pt) {} + pob* mk_pob(pob *parent, unsigned level, unsigned depth, + expr *post, app_ref_vector const &b); + + pob* mk_pob(pob *parent, unsigned level, unsigned depth, + expr *post) { + app_ref_vector b(m_pt.get_ast_manager()); + return mk_pob (parent, level, depth, post, b); + } + + }; + + typedef obj_map rule2expr; + typedef obj_map > rule2apps; + + manager& pm; // spacer-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 + 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 + bool m_all_init; // true if the pt has no uninterpreted body in any rule + ptr_vector m_predicates; + 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; + + void init_sig(); + void ensure_level(unsigned level); + void add_lemma_core (lemma *lemma); + void add_lemma_from_child (pred_transformer &child, lemma *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, 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 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 (); + +public: + pred_transformer(context& ctx, manager& pm, func_decl* head); + ~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 NULL; + } + + 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 (); } + expr_ref get_cover_delta(func_decl* p_orig, int level); + void add_cover(unsigned level, expr* property); + 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 = 0); + /// \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); + /// \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); + + 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, + 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); } + + bool propagate_to_next_level(unsigned level); + void propagate_to_infinity(unsigned level); + /// \brief Add a lemma to the current context and all users + 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 () ;} + + /// 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; + + pob* mk_pob(pob *parent, unsigned level, unsigned depth, + expr *post, app_ref_vector const &b){ + return m_pobs.mk_pob(parent, level, depth, post, b); + } + + pob* mk_pob(pob *parent, unsigned level, unsigned depth, + expr *post) { + return m_pobs.mk_pob(parent, level, depth, post); + } + + lbool 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); + bool is_invariant(unsigned level, expr* lemma, + unsigned& solver_level, expr_ref_vector* core = 0); + bool check_inductive(unsigned level, expr_ref_vector& state, + unsigned& assumes_level); + + expr_ref get_formulas(unsigned level, bool add_axioms); + + void simplify_formulas(); + + context& get_context () const {return ctx;} + 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 ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector& aux_vars, + bool is_init); + + /// \brief Adds a given expression to the set of initial rules + app* extend_initial (expr *e); + + /// \brief Returns true if the obligation is already blocked by current lemmas + bool is_blocked (pob &n, unsigned &uses_level); + /// \brief Returns true if the obligation is already blocked by current quantified lemmas + bool is_qblocked (pob &n); + +}; + + +/** + * A proof obligation. + */ +class pob { + friend class context; + unsigned m_ref_count; + /// parent node + pob_ref m_parent; + /// predicate transformer + pred_transformer& m_pt; + /// post-condition decided by this node + expr_ref m_post; + // if m_post is not ground, then m_binding is an instantiation for + // all quantified variables + app_ref_vector m_binding; + /// new post to be swapped in for m_post + expr_ref m_new_post; + /// level at which to decide the post + unsigned m_level; + + unsigned m_depth; + + /// whether a concrete answer to the post is found + bool m_open; + /// whether to use farkas generalizer to construct a lemma blocking this node + bool m_use_farkas; + + unsigned m_weakness; + /// derivation representing the position of this node in the parent's rule + scoped_ptr m_derivation; + + ptr_vector m_kids; +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); }} + + unsigned weakness() {return m_weakness;} + void bump_weakness() {m_weakness++;} + void reset_weakness() {m_weakness=0;} + + void inc_level () {m_level++; m_depth++;reset_weakness();} + + void inherit(pob const &p); + void set_derivation (derivation *d) {m_derivation = d;} + bool has_derivation () const {return (bool)m_derivation;} + derivation &get_derivation() const {return *m_derivation.get ();} + void reset_derivation () {set_derivation (NULL);} + /// detaches derivation from the node without deallocating + derivation* detach_derivation () {return m_derivation.detach ();} + + pob* parent () const { return m_parent.get (); } + + pred_transformer& pt () const { return m_pt; } + ast_manager& get_ast_manager () const { return m_pt.get_ast_manager (); } + manager& get_manager () const { return m_pt.get_manager (); } + context& get_context () const {return m_pt.get_context ();} + + unsigned level () const { return m_level; } + unsigned depth () const {return m_depth;} + + 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); + + /// indicate that a new post should be set for the node + 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 + void clean(); + + void reset () {clean (); m_derivation = NULL; m_open = true;} + + bool is_closed () const { return !m_open; } + void close(); + + 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 (); } + app_ref_vector const &get_binding() const {return m_binding;} + /* + * Return skolem variables that appear in post + */ + 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); } + } + +}; + + +struct pob_lt : + public std::binary_function +{bool operator() (const pob *pn1, const pob *pn2) const;}; + +struct pob_gt : + public std::binary_function { + pob_lt lt; + bool operator() (const pob *n1, const pob *n2) const + {return lt(n2, n1);} +}; + +struct pob_ref_gt : + public std::binary_function { + pob_gt gt; + bool operator() (const pob_ref &n1, const pob_ref &n2) const + {return gt (n1.get (), n2.get ());} +}; + + +/** + */ +class derivation { + /// a single premise of a derivation + class premise { + pred_transformer &m_pt; + /// origin order in the rule + unsigned m_oidx; + /// summary fact corresponding to the premise + expr_ref m_summary; + /// whether this is a must or may premise + bool m_must; + app_ref_vector m_ovars; + + public: + premise (pred_transformer &pt, unsigned oidx, expr *summary, bool must, + const ptr_vector *aux_vars = NULL); + 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;} + + /// \brief Updated the summary. + /// The new summary is over n-variables. + void set_summary (expr * summary, bool must, + const ptr_vector *aux_vars = NULL); + }; + + + /// parent model node + pob& m_parent; + + /// the rule corresponding to this derivation + const datalog::rule &m_rule; + + /// the premises + vector m_premises; + /// pointer to the active premise + unsigned m_active; + // transition relation over origin variables + expr_ref m_trans; + // implicitly existentially quantified variables in m_trans + 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); +public: + derivation (pob& parent, datalog::rule const& rule, + expr *trans, app_ref_vector const &evars); + void add_premise (pred_transformer &pt, unsigned oidx, + expr * summary, bool must, const ptr_vector *aux_vars = NULL); + + /// 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); + + /// Create the next child. Must summary of the currently active + /// premise must be consistent with the transition relation + pob *create_next_child (); + + datalog::rule const& get_rule () const { return m_rule; } + pob& get_parent () const { return m_parent; } + 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();} +}; + + +class pob_queue { + pob_ref m_root; + unsigned m_max_level; + unsigned m_min_depth; + + std::priority_queue, + pob_ref_gt> m_obligations; + +public: + pob_queue(): m_root(NULL), m_max_level(0), m_min_depth(0) {} + ~pob_queue(); + + void reset(); + pob * top (); + void pop () {m_obligations.pop ();} + void push (pob &n) {m_obligations.push (&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); } + } + + 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;} + unsigned size () {return m_obligations.size ();} + +}; + + +/** + * Generalizers (strengthens) a lemma + */ +class lemma_generalizer { +protected: + context& m_ctx; +public: + lemma_generalizer(context& ctx): m_ctx(ctx) {} + virtual ~lemma_generalizer() {} + virtual void operator()(lemma_ref &lemma) = 0; + virtual void collect_statistics(statistics& st) const {} + virtual void reset_statistics() {} +}; + + +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_num_lemmas; + unsigned m_num_restarts; + stats() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } + }; + + // stat watches + stopwatch m_solve_watch; + stopwatch m_propagate_watch; + stopwatch m_reach_watch; + stopwatch m_is_reach_watch; + stopwatch m_create_children_watch; + stopwatch m_init_rules_watch; + + 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. + func_decl_ref m_query_pred; + pred_transformer* m_query; + mutable pob_queue m_pob_queue; + lbool m_last_result; + unsigned m_inductive_lvl; + unsigned m_expanded_lvl; + ptr_buffer m_lemma_generalizers; + stats m_stats; + 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; + unsigned m_restart_initial_threshold; + + // Functions used by search. + lbool solve_core (unsigned from_lvl = 0); + 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); + expr_ref mk_sat_answer(); + expr_ref mk_unsat_answer() const; + + // Generate inductive property + void get_level_property(unsigned lvl, expr_ref_vector& res, + vector & rs) const; + + + // Initialization + void init_lemma_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_lemma_generalizers(); + + bool validate(); + + unsigned get_cex_depth (); + +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(); + + 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; } + 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 (); + + 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); + + 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); + + 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, expr_ref c); +}; + +inline bool pred_transformer::use_native_mbp () {return ctx.use_native_mbp ();} +} + +#endif diff --git a/src/muz/spacer/spacer_dl_interface.cpp b/src/muz/spacer/spacer_dl_interface.cpp new file mode 100644 index 000000000..264809f72 --- /dev/null +++ b/src/muz/spacer/spacer_dl_interface.cpp @@ -0,0 +1,354 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_dl.cpp + +Abstract: + + SMT2 interface for the datalog SPACER + +Author: + + Arie Gurfinkel + +Revision History: + +--*/ + +#include "dl_context.h" +#include "dl_mk_coi_filter.h" +#include "dl_mk_interp_tail_simplifier.h" +#include "dl_mk_subsumption_checker.h" +#include "dl_mk_rule_inliner.h" +#include "dl_rule.h" +#include "dl_rule_transformer.h" +#include "smt2parser.h" +#include "spacer_context.h" +#include "spacer_dl_interface.h" +#include "dl_rule_set.h" +#include "dl_mk_slice.h" +#include "dl_mk_unfold.h" +#include "dl_mk_coalesce.h" +#include "model_smt2_pp.h" +#include "scoped_proof.h" +#include "dl_transforms.h" + +using namespace spacer; + +dl_interface::dl_interface(datalog::context& ctx) : + engine_base(ctx.get_manager(), "spacer"), + m_ctx(ctx), + m_spacer_rules(ctx), + m_old_rules(ctx), + m_context(0), + m_refs(ctx.get_manager()) +{ + m_context = alloc(spacer::context, 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("spacer", 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, 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); + ); + + + 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(); + + IF_VERBOSE(2, m_ctx.display_rules(verbose_stream());); + m_spacer_rules.replace_rules(rules); + m_spacer_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_spacer_rules); + + if (m_spacer_rules.get_rules().empty()) { + m_context->set_unsat(); + IF_VERBOSE(2, model_smt2_pp(verbose_stream(), m, *m_context->get_model(), 0);); + return l_false; + } + + return m_context->solve(); + +} + +lbool dl_interface::query_from_lvl(expr * query, unsigned lvl) +{ + //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, 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); + ); + + + 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(); + + IF_VERBOSE(2, m_ctx.display_rules(verbose_stream());); + m_spacer_rules.replace_rules(rules); + m_spacer_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_spacer_rules); + + if (m_spacer_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(lvl); + +} + +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); +} + +void dl_interface::add_invariant(func_decl* pred, expr* property) +{ + if (m_ctx.get_params().xform_slice()) { + throw default_exception("Invariants are incompatible with slicing. Disable slicing before using invariants"); + } + m_context->add_invariant(pred, property); +} + +expr_ref dl_interface::get_reachable(func_decl* pred) +{ + if (m_ctx.get_params().xform_slice()) { + throw default_exception("Invariants are incompatible with slicing. " + "Disable slicing before using invariants"); + } + return m_context->get_reachable(pred); +} + +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(); +} + +expr_ref dl_interface::get_ground_sat_answer() +{ + return m_context->get_ground_sat_answer(); +} + +void dl_interface::get_rules_along_trace(datalog::rule_ref_vector& rules) +{ + m_context->get_rules_along_trace(rules); +} + +void dl_interface::updt_params() +{ + dealloc(m_context); + m_context = alloc(spacer::context, 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/spacer/spacer_dl_interface.h b/src/muz/spacer/spacer_dl_interface.h new file mode 100644 index 000000000..809f6fc83 --- /dev/null +++ b/src/muz/spacer/spacer_dl_interface.h @@ -0,0 +1,86 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_dl_interface.h + +Abstract: + + SMT2 interface for the datalog SPACER + +Author: + + +Revision History: + +--*/ + +#ifndef _SPACER_DL_INTERFACE_H_ +#define _SPACER_DL_INTERFACE_H_ + +#include "lbool.h" +#include "dl_rule.h" +#include "dl_rule_set.h" +#include "dl_engine_base.h" +#include "statistics.h" + +namespace datalog { +class context; +} + +namespace spacer { + +class context; + +class dl_interface : public datalog::engine_base { + datalog::context& m_ctx; + datalog::rule_set m_spacer_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(); + + lbool query(expr* query); + + lbool query_from_lvl(expr* query, unsigned lvl); + + void display_certificate(std::ostream& out) const; + + void collect_statistics(statistics& st) const; + + void reset_statistics(); + + expr_ref get_answer(); + + expr_ref get_ground_sat_answer(); + + void get_rules_along_trace(datalog::rule_ref_vector& rules); + + unsigned get_num_levels(func_decl* pred); + + expr_ref get_cover_delta(int level, func_decl* pred); + + void add_cover(int level, func_decl* pred, expr* property); + + void add_invariant(func_decl* pred, expr* property); + + expr_ref get_reachable(func_decl *pred); + + void updt_params(); + + model_ref get_model(); + + proof_ref get_proof(); + +}; +} + + +#endif diff --git a/src/muz/spacer/spacer_farkas_learner.cpp b/src/muz/spacer/spacer_farkas_learner.cpp new file mode 100644 index 000000000..edee8ba6f --- /dev/null +++ b/src/muz/spacer/spacer_farkas_learner.cpp @@ -0,0 +1,440 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_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: +// TODO: what to write here +--*/ + +//TODO: reorder, delete unnecessary includes +#include "ast_smt2_pp.h" +#include "array_decl_plugin.h" +#include "bool_rewriter.h" +#include "dl_decl_plugin.h" +#include "for_each_expr.h" +#include "dl_util.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "spacer_util.h" +#include "spacer_farkas_learner.h" +#include "th_rewriter.h" +#include "ast_ll_pp.h" +#include "proof_utils.h" +#include "reg_decl_plugins.h" +#include "smt_farkas_util.h" + +namespace spacer { + +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 farkas_learner::combine_constraints(unsigned n, app * const * lits, rational const * coeffs, expr_ref& res) +{ + ast_manager& m = res.get_manager(); + smt::farkas_util res_c(m); + res_c.set_split_literals(m_split_literals); + for (unsigned i = 0; i < n; ++i) { + res_c.add(coeffs[i], lits[i]); + } + res = res_c.get(); +} + +// every uninterpreted symbol is in symbs +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 farkas_learner::is_pure_expr(func_decl_set const& symbs, expr* e, ast_manager& m) const +{ + is_pure_expr_proc proc(symbs, m); + 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("spacer_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); + datalog::set_union(*hyps3, *hyps); + datalog::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, m)) { + TRACE("farkas_learner2", + 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), m)) { + a_depend.mark(p, true); + } + break; + } + case PR_LEMMA: { + expr_set* hyps2 = alloc(expr_set); + hyprefs.push_back(hyps2); + datalog::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_learner2", + 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_learner2", 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_bounds(lemmas); +} + +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); + TRACE("farkas_learner2", + 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; +} +} + diff --git a/src/muz/spacer/spacer_farkas_learner.h b/src/muz/spacer/spacer_farkas_learner.h new file mode 100644 index 000000000..7a0abf6f5 --- /dev/null +++ b/src/muz/spacer/spacer_farkas_learner.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_farkas_learner.h + +Abstract: + + SMT2 interface for the datalog SPACER + +Author: + + Krystof Hoder (t-khoder) 2011-11-1. + +Revision History: + +--*/ + +#ifndef _SPACER_FARKAS_LEARNER_H_ +#define _SPACER_FARKAS_LEARNER_H_ + +#include "ast.h" + +namespace spacer { + + + + + + + +class farkas_learner { + typedef obj_hashtable expr_set; + + bool m_split_literals; + + void combine_constraints(unsigned cnt, app * const * constrs, rational const * coeffs, expr_ref& res); + + 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, ast_manager& m) const; + +public: + farkas_learner(): m_split_literals(false) {} + + /** + Traverse a proof and retrieve lemmas using the vocabulary from bs. + */ + void get_lemmas(proof* root, expr_set const& bs, expr_ref_vector& lemmas); + + void collect_statistics(statistics& st) const {} + void reset_statistics() {} + + + /** \brief see smt::farkas_util::set_split_literals */ + void set_split_literals(bool v) {m_split_literals = v;} + +}; + + +} + +#endif diff --git a/src/muz/spacer/spacer_generalizers.cpp b/src/muz/spacer/spacer_generalizers.cpp new file mode 100644 index 000000000..f36983077 --- /dev/null +++ b/src/muz/spacer/spacer_generalizers.cpp @@ -0,0 +1,294 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_generalizers.cpp + +Abstract: + + Lemma generalizers. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-20. + Arie Gurfinkel + +Revision History: + +--*/ + + +#include "spacer_context.h" +#include "spacer_generalizers.h" +#include "expr_abstract.h" +#include "var_subst.h" +#include "for_each_expr.h" +#include "obj_equiv_class.h" + + +namespace spacer { +void lemma_sanity_checker::operator()(lemma_ref &lemma) { + unsigned uses_level; + 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)); +} + + +// ------------------------ +// lemma_bool_inductive_generalizer +/// Inductive generalization by dropping and expanding literals +void lemma_bool_inductive_generalizer::operator()(lemma_ref &lemma) { + if (lemma->get_cube().empty()) return; + + m_st.count++; + scoped_watch _w_(m_st.watch); + + unsigned uses_level; + pred_transformer &pt = lemma->get_pob()->pt(); + ast_manager &m = pt.get_ast_manager(); + + expr_ref_vector cube(m); + cube.append(lemma->get_cube()); + + bool dirty = false; + expr_ref true_expr(m.mk_true(), m); + ptr_vector processed; + expr_ref_vector extra_lits(m); + + 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); + cube[i] = true_expr; + if (cube.size() > 1 && + pt.check_inductive(lemma->level(), cube, uses_level)) { + num_failures = 0; + dirty = true; + for (i = 0; i < cube.size() && + processed.contains(cube.get(i)); ++i); + } else { + // check if the literal can be expanded and any single + // literal in the expansion can replace it + extra_lits.reset(); + extra_lits.push_back(lit); + expand_literals(m, extra_lits); + SASSERT(extra_lits.size() > 0); + bool found = false; + if (extra_lits.get(0) != lit) { + 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)) { + num_failures = 0; + dirty = true; + found = true; + processed.push_back(extra_lits.get(j)); + for (i = 0; i < cube.size() && + processed.contains(cube.get(i)); ++i); + } + } + } + if (!found) { + cube[i] = lit; + processed.push_back(lit); + ++num_failures; + ++m_st.num_failures; + ++i; + } + } + } + + if (dirty) { + TRACE("spacer", + tout << "Generalized from:\n" << mk_and(lemma->get_cube()) + << "\ninto\n" << mk_and(cube) << "\n";); + + lemma->update_cube(lemma->get_pob(), cube); + SASSERT(uses_level >= lemma->level()); + lemma->set_level(uses_level); + } +} + +void lemma_bool_inductive_generalizer::collect_statistics(statistics &st) const +{ + st.update("time.spacer.solve.reach.gen.bool_ind", m_st.watch.get_seconds()); + st.update("bool inductive gen", m_st.count); + st.update("bool inductive gen failures", m_st.num_failures); +} + +void unsat_core_generalizer::operator()(lemma_ref &lemma) +{ + m_st.count++; + scoped_watch _w_(m_st.watch); + ast_manager &m = lemma->get_ast_manager(); + + pred_transformer &pt = lemma->get_pob()->pt(); + + unsigned old_sz = lemma->get_cube().size(); + unsigned old_level = lemma->level(); + + unsigned uses_level; + expr_ref_vector core(m); + bool r; + r = pt.is_invariant(lemma->level(), lemma->get_expr(), uses_level, &core); + SASSERT(r); + + CTRACE("spacer", old_sz > core.size(), + tout << "unsat core reduced lemma from: " + << old_sz << " to " << core.size() << "\n";); + CTRACE("spacer", old_level < uses_level, + tout << "unsat core moved lemma up from: " + << old_level << " to " << uses_level << "\n";); + if (old_sz > core.size()) { + lemma->update_cube(lemma->get_pob(), core); + lemma->set_level(uses_level); + } +} + +void unsat_core_generalizer::collect_statistics(statistics &st) const +{ + st.update("time.spacer.solve.reach.gen.unsat_core", m_st.watch.get_seconds()); + st.update("gen.unsat_core.cnt", m_st.count); + st.update("gen.unsat_core.fail", m_st.num_failures); +} + +namespace { +class collect_array_proc { + array_util m_au; + func_decl_set &m_symbs; + sort *m_sort; +public: + collect_array_proc(ast_manager &m, func_decl_set& s) : + m_au(m), m_symbs(s), m_sort(NULL) {} + + void operator()(app* a) + { + if (a->get_family_id() == null_family_id && m_au.is_array(a)) { + if (m_sort && m_sort != get_sort(a)) { return; } + if (!m_sort) { m_sort = get_sort(a); } + m_symbs.insert(a->get_decl()); + } + } + void operator()(var*) {} + void operator()(quantifier*) {} +}; +} + +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(); + + expr_ref_vector core(m); + expr_ref v(m); + func_decl_set symb; + collect_array_proc cap(m, symb); + + core.append (lemma->get_cube()); + v = mk_and(core); + for_each_expr(cap, v); + + TRACE("core_array_eq", + tout << "found " << symb.size() << " array variables in: \n" + << mk_pp(v, m) << "\n";); + + // too few constants + if (symb.size() <= 1) { return; } + // too many constants, skip this + if (symb.size() >= 8) { return; } + + + // -- for every pair of variables, try an equality + typedef func_decl_set::iterator iterator; + ptr_vector vsymbs; + for (iterator it = symb.begin(), end = symb.end(); + it != end; ++it) + { vsymbs.push_back(*it); } + + 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)))); } + + smt::kernel solver(m, m_ctx.get_manager().fparams2()); + 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); + + 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))); + 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)))); + // } + + TRACE("core_array_eq", tout << "new possible core " + << mk_pp(pm.mk_and(lits), m) << "\n";); + + + pred_transformer &pt = lemma->get_pob()->pt(); + // -- check if it is consistent with the transition relation + unsigned uses_level1; + if (pt.check_inductive(lemma->level(), lits, uses_level1)) { + TRACE("core_array_eq", tout << "Inductive!\n";); + lemma->update_cube(lemma->get_pob(),lits); + lemma->set_level(uses_level1); + return; + } 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()); + + 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) { + lemma->update_cube(lemma->get_pob(), core); + } +} +}; diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h new file mode 100644 index 000000000..8634f0828 --- /dev/null +++ b/src/muz/spacer/spacer_generalizers.h @@ -0,0 +1,99 @@ +/*++ +Copyright (c) 2017 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_generalizers.h + +Abstract: + + Generalizer plugins. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-22. + Arie Gurfinkel +Revision History: + +--*/ + +#ifndef _SPACER_GENERALIZERS_H_ +#define _SPACER_GENERALIZERS_H_ + +#include "spacer_context.h" +#include "arith_decl_plugin.h" + +namespace spacer { + +// can be used to check whether produced core is really implied by +// frame and therefore valid TODO: or negation? +class lemma_sanity_checker : public lemma_generalizer { +public: + lemma_sanity_checker(context& ctx) : lemma_generalizer(ctx) {} + virtual ~lemma_sanity_checker() {} + virtual void operator()(lemma_ref &lemma); +}; + +/** + * Boolean inductive generalization by dropping literals + */ +class lemma_bool_inductive_generalizer : public lemma_generalizer { + + struct stats { + unsigned count; + unsigned num_failures; + stopwatch watch; + stats() {reset();} + void reset() {count = 0; num_failures = 0; watch.reset();} + }; + + unsigned m_failure_limit; + stats m_st; + +public: + lemma_bool_inductive_generalizer(context& ctx, unsigned failure_limit) : + lemma_generalizer(ctx), m_failure_limit(failure_limit) {} + virtual ~lemma_bool_inductive_generalizer() {} + virtual void operator()(lemma_ref &lemma); + + virtual void collect_statistics(statistics& st) const; + virtual void reset_statistics() {m_st.reset();} +}; + +class unsat_core_generalizer : public lemma_generalizer { + struct stats { + unsigned count; + unsigned num_failures; + stopwatch watch; + stats() { reset(); } + void reset() {count = 0; num_failures = 0; watch.reset();} + }; + + stats m_st; +public: + unsat_core_generalizer(context &ctx) : lemma_generalizer(ctx) {} + virtual ~unsat_core_generalizer() {} + virtual void operator()(lemma_ref &lemma); + + virtual void collect_statistics(statistics &st) const; + virtual void reset_statistics() {m_st.reset();} +}; + +class lemma_array_eq_generalizer : public lemma_generalizer { +public: + lemma_array_eq_generalizer(context &ctx) : lemma_generalizer(ctx) {} + virtual ~lemma_array_eq_generalizer() {} + virtual void operator()(lemma_ref &lemma); + +}; + +class lemma_eq_generalizer : public lemma_generalizer { +public: + lemma_eq_generalizer(context &ctx) : lemma_generalizer(ctx) {} + virtual ~lemma_eq_generalizer() {} + virtual void operator()(lemma_ref &lemma); +}; + + +}; +#endif diff --git a/src/muz/spacer/spacer_itp_solver.cpp b/src/muz/spacer/spacer_itp_solver.cpp new file mode 100644 index 000000000..7571352b3 --- /dev/null +++ b/src/muz/spacer/spacer_itp_solver.cpp @@ -0,0 +1,355 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_itp_solver.cpp + +Abstract: + + A solver that produces interpolated unsat cores + +Author: + + Arie Gurfinkel + +Notes: + +--*/ +#include"spacer_itp_solver.h" +#include"ast.h" +#include"spacer_util.h" +#include"spacer_farkas_learner.h" +#include"expr_replacer.h" +#include "spacer_unsat_core_learner.h" +#include "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 = NULL; + 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 new file mode 100644 index 000000000..bf5a00751 --- /dev/null +++ b/src/muz/spacer/spacer_itp_solver.h @@ -0,0 +1,177 @@ +/** +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.h" +#include "expr_substitution.h" +#include"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) + {} + + virtual ~itp_solver() {} + + /* itp solver specific */ + virtual void get_unsat_core(expr_ref_vector &core); + 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 */ + + virtual solver* translate(ast_manager &m, params_ref const &p) + {return m_solver.translate(m, p);} + virtual void updt_params(params_ref const &p) + {m_solver.updt_params(p);} + virtual void collect_param_descrs(param_descrs &r) + {m_solver.collect_param_descrs(r);} + virtual void set_produce_models(bool f) + {m_solver.set_produce_models(f);} + virtual void assert_expr(expr *t) + {m_solver.assert_expr(t);} + + virtual void assert_expr(expr *t, expr *a) + {NOT_IMPLEMENTED_YET();} + + virtual void push(); + virtual void pop(unsigned n); + virtual unsigned get_scope_level() const + {return m_solver.get_scope_level();} + + virtual lbool check_sat(unsigned num_assumptions, expr * const *assumptions); + virtual void set_progress_callback(progress_callback *callback) + {m_solver.set_progress_callback(callback);} + virtual unsigned get_num_assertions() const + {return m_solver.get_num_assertions();} + virtual expr * get_assertion(unsigned idx) const + {return m_solver.get_assertion(idx);} + virtual unsigned get_num_assumptions() const + {return m_solver.get_num_assumptions();} + virtual expr * get_assumption(unsigned idx) const + {return m_solver.get_assumption(idx);} + virtual std::ostream &display(std::ostream &out) const + {m_solver.display(out); return out;} + + /* check_sat_result interface */ + + virtual void collect_statistics(statistics &st) const ; + virtual void reset_statistics(); + virtual void get_unsat_core(ptr_vector &r); + virtual void get_model(model_ref &m) {m_solver.get_model(m);} + virtual proof *get_proof() {return m_solver.get_proof();} + virtual std::string reason_unknown() const + {return m_solver.reason_unknown();} + virtual void set_reason_unknown(char const* msg) + {m_solver.set_reason_unknown(msg);} + virtual void get_labels(svector &r) + {m_solver.get_labels(r);} + virtual ast_manager &get_manager() const {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_legacy_frames.cpp b/src/muz/spacer/spacer_legacy_frames.cpp new file mode 100644 index 000000000..c998121c6 --- /dev/null +++ b/src/muz/spacer/spacer_legacy_frames.cpp @@ -0,0 +1,170 @@ +/* + Copyright (c) 2017 Arie Gurfinkel + + Legacy implementations of frames. To be removed. + */ +#include "spacer_context.h" +#include +#include + +#include "dl_util.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "var_subst.h" +#include "util.h" +#include "spacer_prop_solver.h" +#include "spacer_context.h" +#include "spacer_generalizers.h" +#include "for_each_expr.h" +#include "dl_rule_set.h" +#include "unit_subsumption_tactic.h" +#include "model_smt2_pp.h" +#include "dl_mk_rule_inliner.h" +#include "ast_smt2_pp.h" +#include "ast_ll_pp.h" +#include "ast_util.h" +#include "proof_checker.h" +#include "smt_value_sort.h" +#include "proof_utils.h" +#include "scoped_proof.h" +#include "spacer_qe_project.h" +#include "blast_term_ite_tactic.h" + +#include "timeit.h" +#include "luby.h" +#include "expr_safe_replace.h" +#include "expr_abstract.h" +#include "obj_equiv_class.h" + + +namespace spacer { +// ------------------ +// legacy_frames +void pred_transformer::legacy_frames::simplify_formulas(tactic& tac, + expr_ref_vector& v) +{ + ast_manager &m = m_pt.get_ast_manager(); + goal_ref g(alloc(goal, m, false, false, false)); + for (unsigned j = 0; j < v.size(); ++j) { g->assert_expr(v[j].get()); } + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref core(m); + goal_ref_buffer result; + tac(g, result, mc, pc, core); + 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::legacy_frames::simplify_formulas() +{ + ast_manager &m = m_pt.get_ast_manager(); + tactic_ref us = mk_unit_subsumption_tactic(m); + simplify_formulas(*us, m_invariants); + for (unsigned i = 0; i < m_levels.size(); ++i) { + simplify_formulas(*us, m_levels[i]); + } +} + +void pred_transformer::legacy_frames::get_frame_geq_lemmas(unsigned lvl, + expr_ref_vector &out) +{ + get_frame_lemmas(infty_level(), out); + for (unsigned i = lvl, sz = m_levels.size(); i < sz; ++i) + { get_frame_lemmas(i, out); } +} + +bool pred_transformer::legacy_frames::propagate_to_next_level(unsigned src_level) +{ + + ast_manager &m = m_pt.get_ast_manager(); + if (m_levels.size() <= src_level) { return true; } + if (m_levels [src_level].empty()) { return true; } + + unsigned tgt_level = next_level(src_level); + m_pt.ensure_level(next_level(tgt_level)); + + TRACE("spacer", + tout << "propagating " << src_level << " to " << tgt_level; + tout << " for relation " << m_pt.head()->get_name() << "\n";); + + for (unsigned i = 0; i < m_levels[src_level].size();) { + expr_ref_vector &src = m_levels[src_level]; + expr * curr = src[i].get(); + unsigned stored_lvl; + VERIFY(m_prop2level.find(curr, stored_lvl)); + SASSERT(stored_lvl >= src_level); + unsigned solver_level; + if (stored_lvl > src_level) { + TRACE("spacer", tout << "at level: " << stored_lvl << " " << mk_pp(curr, m) << "\n";); + src[i] = src.back(); + src.pop_back(); + } else if (m_pt.is_invariant(tgt_level, curr, solver_level)) { + // -- might invalidate src reference + add_lemma(curr, solver_level); + TRACE("spacer", tout << "is invariant: " << pp_level(solver_level) << " " << mk_pp(curr, m) << "\n";); + // shadow higher-level src + expr_ref_vector &src = m_levels[src_level]; + src[i] = src.back(); + src.pop_back(); + ++m_pt.m_stats.m_num_propagations; + } else { + TRACE("spacer", tout << "not propagated: " << mk_pp(curr, m) << "\n";); + ++i; + } + } + + CTRACE("spacer", m_levels[src_level].empty(), + tout << "Fully propagated level " + << src_level << " of " << m_pt.head()->get_name() << "\n";); + + return m_levels[src_level].empty(); +} + +bool pred_transformer::legacy_frames::add_lemma(expr * lemma, unsigned lvl) +{ + if (is_infty_level(lvl)) { + if (!m_invariants.contains(lemma)) { + m_invariants.push_back(lemma); + m_prop2level.insert(lemma, lvl); + //m_pt.add_lemma_core (lemma, lvl); + return true; + } + return false; + } + + unsigned old_level; + if (!m_prop2level.find(lemma, old_level) || old_level < lvl) { + m_levels[lvl].push_back(lemma); + m_prop2level.insert(lemma, lvl); + //m_pt.add_lemma_core (lemma, lvl); + return true; + } + return false; +} + +void pred_transformer::legacy_frames::propagate_to_infinity(unsigned level) +{ + TRACE("spacer", tout << "propagating to oo from lvl " << level + << " of " << m_pt.m_head->get_name() << "\n";); + + if (m_levels.empty()) { return; } + + for (unsigned i = m_levels.size(); i > level; --i) { + expr_ref_vector &lemmas = m_levels [i - 1]; + for (unsigned j = 0; j < lemmas.size(); ++j) + { add_lemma(lemmas.get(j), infty_level()); } + lemmas.reset(); + } +} + +void pred_transformer::legacy_frames::inherit_frames(legacy_frames& other) +{ + + SASSERT(m_pt.m_head == other.m_pt.m_head); + obj_map::iterator it = other.m_prop2level.begin(); + obj_map::iterator end = other.m_prop2level.end(); + for (; it != end; ++it) { add_lemma(it->m_key, it->m_value); } +} +} diff --git a/src/muz/spacer/spacer_legacy_frames.h b/src/muz/spacer/spacer_legacy_frames.h new file mode 100644 index 000000000..7ff4b6fad --- /dev/null +++ b/src/muz/spacer/spacer_legacy_frames.h @@ -0,0 +1,47 @@ +/**++ + Copyright (c) 2017 Arie Gurfinkel + + Legacy implementations of frames. To be removed. + + Notes: this file is included from the middle of spacer_context.h +*/ + +class legacy_frames +{ + pred_transformer &m_pt; + + /// level formulas + vector m_levels; + /// map property to level where it occurs. + obj_map m_prop2level; + /// properties that are invariant. + expr_ref_vector m_invariants; + + void simplify_formulas (tactic& tac, expr_ref_vector& v); + +public: + legacy_frames (pred_transformer &pt) : + m_pt(pt), m_invariants (m_pt.get_ast_manager ()) {} + pred_transformer& pt () const {return m_pt;} + bool add_lemma (expr * lemma, unsigned level); + void get_frame_lemmas (unsigned level, expr_ref_vector &out) + { + if(is_infty_level(level)) { out.append(m_invariants); } + else if(level < m_levels.size()) { out.append(m_levels [level]); } + } + + void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out); + void add_frame () {m_levels.push_back (expr_ref_vector (m_pt.get_ast_manager ()));} + + unsigned size () const {return m_levels.size ();} + unsigned lemma_size () const {return m_prop2level.size ();} + + + void propagate_to_infinity (unsigned level); + bool propagate_to_next_level (unsigned level); + + void simplify_formulas (); + + void inherit_frames (legacy_frames& other); + +}; diff --git a/src/muz/spacer/spacer_legacy_mbp.cpp b/src/muz/spacer/spacer_legacy_mbp.cpp new file mode 100644 index 000000000..a9569004c --- /dev/null +++ b/src/muz/spacer/spacer_legacy_mbp.cpp @@ -0,0 +1,116 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_legacy_mbp.cpp + +Abstract: + + Legacy Model Based Projection. Used by Grigory Fedyukovich + +Author: + + Arie Gurfinkel + Anvesh Komuravelli +Notes: + +--*/ +#include +#include "arith_simplifier_plugin.h" +#include "array_decl_plugin.h" +#include "ast_pp.h" +#include "basic_simplifier_plugin.h" +#include "bv_simplifier_plugin.h" +#include "bool_rewriter.h" +#include "dl_util.h" +#include "for_each_expr.h" +#include "smt_params.h" +#include "model.h" +#include "ref_vector.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "util.h" +#include "spacer_manager.h" +#include "spacer_util.h" +#include "arith_decl_plugin.h" +#include "expr_replacer.h" +#include "model_smt2_pp.h" +#include "scoped_proof.h" +#include "qe_lite.h" +#include "spacer_qe_project.h" +#include "model_pp.h" +#include "expr_safe_replace.h" + +#include "datatype_decl_plugin.h" +#include "bv_decl_plugin.h" + +#include "spacer_legacy_mev.h" + +namespace spacer { +void qe_project(ast_manager& m, app_ref_vector& vars, expr_ref& fml, model_ref& M, expr_map& map) +{ + th_rewriter rw(m); + // qe-lite; TODO: use qe_lite aggressively + params_ref p; + qe_lite qe(m, p, true); + qe(vars, fml); + rw(fml); + + TRACE("spacer", + 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"; + } + ); + + // substitute model values for booleans and + // use LW projection for arithmetic variables + if (!vars.empty()) { + app_ref_vector arith_vars(m); + expr_substitution sub(m); + proof_ref pr(m.mk_asserted(m.mk_true()), m); + expr_ref bval(m); + 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)); + sub.insert(vars.get(i), bval, pr); + } else { + arith_vars.push_back(vars.get(i)); + } + } + if (!sub.empty()) { + scoped_ptr rep = mk_expr_simp_replacer(m); + rep->set_substitution(&sub); + (*rep)(fml); + rw(fml); + TRACE("spacer", + tout << "Projected Boolean vars:\n" << mk_pp(fml, m) << "\n"; + ); + } + // model based projection + if (!arith_vars.empty()) { + TRACE("spacer", + tout << "Arith vars:\n"; + for (unsigned i = 0; i < arith_vars.size(); ++i) { + tout << mk_pp(arith_vars.get(i), m) << "\n"; + } + ); + { + scoped_no_proof _sp(m); + 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 + vars.reset(); + vars.append(arith_vars); + } +} +} diff --git a/src/muz/spacer/spacer_legacy_mev.cpp b/src/muz/spacer/spacer_legacy_mev.cpp new file mode 100644 index 000000000..0ab33c693 --- /dev/null +++ b/src/muz/spacer/spacer_legacy_mev.cpp @@ -0,0 +1,837 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + + Deprecated implementation of model evaluator. To be removed. +*/ + +#include +#include "arith_simplifier_plugin.h" +#include "array_decl_plugin.h" +#include "ast_pp.h" +#include "basic_simplifier_plugin.h" +#include "bv_simplifier_plugin.h" +#include "bool_rewriter.h" +#include "dl_util.h" +#include "for_each_expr.h" +#include "smt_params.h" +#include "model.h" +#include "ref_vector.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "util.h" +#include "spacer_manager.h" +#include "spacer_legacy_mev.h" +#include "spacer_util.h" +#include "arith_decl_plugin.h" +#include "expr_replacer.h" +#include "model_smt2_pp.h" +#include "scoped_proof.h" +#include "qe_lite.h" +#include "spacer_qe_project.h" +#include "model_pp.h" +#include "expr_safe_replace.h" + +#include "datatype_decl_plugin.h" +#include "bv_decl_plugin.h" + +namespace old { + +///////////////////////// +// model_evaluator +// + + +void model_evaluator::assign_value(expr* e, expr* val) +{ + rational r; + if (m.is_true(val)) { + set_true(e); + } else if (m.is_false(val)) { + set_false(e); + } else if (m_arith.is_numeral(val, r)) { + set_number(e, r); + } else if (m.is_value(val)) { + set_value(e, val); + } else { + IF_VERBOSE(3, verbose_stream() << "Not evaluated " << mk_pp(e, m) << "\n";); + TRACE("old_spacer", tout << "Variable is not tracked: " << mk_pp(e, m) << "\n";); + set_x(e); + } +} + +void model_evaluator::setup_model(const model_ref& model) +{ + m_numbers.reset(); + m_values.reset(); + m_model = model.get(); + rational r; + unsigned sz = model->get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = model->get_constant(i); + expr* val = model->get_const_interp(d); + expr* e = m.mk_const(d); + m_refs.push_back(e); + assign_value(e, val); + } +} + +void model_evaluator::reset() +{ + m1.reset(); + m2.reset(); + m_values.reset(); + m_visited.reset(); + m_numbers.reset(); + m_refs.reset(); + m_model = 0; +} + + +void model_evaluator::minimize_literals(ptr_vector const& formulas, + const model_ref& mdl, expr_ref_vector& result) +{ + + TRACE("old_spacer", + tout << "formulas:\n"; + for (unsigned i = 0; i < formulas.size(); ++i) tout << mk_pp(formulas[i], m) << "\n"; + ); + + expr_ref tmp(m); + ptr_vector tocollect; + + setup_model(mdl); + collect(formulas, tocollect); + for (unsigned i = 0; i < tocollect.size(); ++i) { + expr* e = tocollect[i]; + expr* e1, *e2; + SASSERT(m.is_bool(e)); + SASSERT(is_true(e) || is_false(e)); + if (is_true(e)) { + result.push_back(e); + } + // hack to break disequalities for arithmetic variables. + else if (m.is_eq(e, e1, e2) && m_arith.is_int_real(e1)) { + if (get_number(e1) < get_number(e2)) { + result.push_back(m_arith.mk_lt(e1, e2)); + } else { + result.push_back(m_arith.mk_lt(e2, e1)); + } + } else { + result.push_back(m.mk_not(e)); + } + } + reset(); + TRACE("old_spacer", + tout << "minimized model:\n"; + for (unsigned i = 0; i < result.size(); ++i) tout << mk_pp(result[i].get(), m) << "\n"; + ); +} + +void model_evaluator::process_formula(app* e, ptr_vector& todo, ptr_vector& tocollect) +{ + SASSERT(m.is_bool(e)); + SASSERT(is_true(e) || is_false(e)); + unsigned v = is_true(e); + unsigned sz = e->get_num_args(); + expr* const* args = e->get_args(); + if (e->get_family_id() == m.get_basic_family_id()) { + switch (e->get_decl_kind()) { + case OP_TRUE: + break; + case OP_FALSE: + break; + case OP_EQ: + case OP_IFF: + if (args[0] == args[1]) { + SASSERT(v); + // no-op + } else if (m.is_bool(args[0])) { + todo.append(sz, args); + } else { + tocollect.push_back(e); + } + break; + case OP_DISTINCT: + tocollect.push_back(e); + break; + case OP_ITE: + if (args[1] == args[2]) { + tocollect.push_back(args[1]); + } else if (is_true(args[1]) && is_true(args[2])) { + todo.append(2, args + 1); + } else if (is_false(args[1]) && is_false(args[2])) { + todo.append(2, args + 1); + } else if (is_true(args[0])) { + todo.append(2, args); + } else { + SASSERT(is_false(args[0])); + todo.push_back(args[0]); + todo.push_back(args[2]); + } + break; + case OP_AND: + if (v) { + todo.append(sz, args); + } else { + unsigned i = 0; + for (; !is_false(args[i]) && i < sz; ++i); + if (i == sz) { + fatal_error(1); + } + VERIFY(i < sz); + todo.push_back(args[i]); + } + break; + case OP_OR: + if (v) { + unsigned i = 0; + for (; !is_true(args[i]) && i < sz; ++i); + if (i == sz) { + fatal_error(1); + } + VERIFY(i < sz); + todo.push_back(args[i]); + } else { + todo.append(sz, args); + } + break; + case OP_XOR: + case OP_NOT: + todo.append(sz, args); + break; + case OP_IMPLIES: + if (v) { + if (is_true(args[1])) { + todo.push_back(args[1]); + } else if (is_false(args[0])) { + todo.push_back(args[0]); + } else { + IF_VERBOSE(0, verbose_stream() << "Term not handled " << mk_pp(e, m) << "\n";); + UNREACHABLE(); + } + } else { + todo.append(sz, args); + } + break; + default: + IF_VERBOSE(0, verbose_stream() << "Term not handled " << mk_pp(e, m) << "\n";); + UNREACHABLE(); + } + } else { + tocollect.push_back(e); + } +} + +void model_evaluator::collect(ptr_vector const& formulas, ptr_vector& tocollect) +{ + ptr_vector todo; + todo.append(formulas); + m_visited.reset(); + + VERIFY(check_model(formulas)); + + while (!todo.empty()) { + app* e = to_app(todo.back()); + todo.pop_back(); + if (!m_visited.is_marked(e)) { + process_formula(e, todo, tocollect); + m_visited.mark(e, true); + } + } + m_visited.reset(); +} + +void model_evaluator::eval_arith(app* e) +{ + rational r, r2; + +#define ARG1 e->get_arg(0) +#define ARG2 e->get_arg(1) + + unsigned arity = e->get_num_args(); + for (unsigned i = 0; i < arity; ++i) { + expr* arg = e->get_arg(i); + if (is_x(arg)) { + set_x(e); + return; + } + SASSERT(!is_unknown(arg)); + } + switch (e->get_decl_kind()) { + case OP_NUM: + VERIFY(m_arith.is_numeral(e, r)); + set_number(e, r); + break; + case OP_IRRATIONAL_ALGEBRAIC_NUM: + set_x(e); + break; + case OP_LE: + set_bool(e, get_number(ARG1) <= get_number(ARG2)); + break; + case OP_GE: + set_bool(e, get_number(ARG1) >= get_number(ARG2)); + break; + case OP_LT: + set_bool(e, get_number(ARG1) < get_number(ARG2)); + break; + case OP_GT: + set_bool(e, get_number(ARG1) > get_number(ARG2)); + break; + case OP_ADD: + r = rational::zero(); + for (unsigned i = 0; i < arity; ++i) { + r += get_number(e->get_arg(i)); + } + set_number(e, r); + break; + case OP_SUB: + r = get_number(e->get_arg(0)); + for (unsigned i = 1; i < arity; ++i) { + r -= get_number(e->get_arg(i)); + } + set_number(e, r); + break; + case OP_UMINUS: + SASSERT(arity == 1); + set_number(e, -get_number(e->get_arg(0))); + break; + case OP_MUL: + r = rational::one(); + for (unsigned i = 0; i < arity; ++i) { + r *= get_number(e->get_arg(i)); + } + set_number(e, r); + break; + case OP_DIV: + SASSERT(arity == 2); + r = get_number(ARG2); + if (r.is_zero()) { + set_x(e); + } else { + set_number(e, get_number(ARG1) / r); + } + break; + case OP_IDIV: + SASSERT(arity == 2); + r = get_number(ARG2); + if (r.is_zero()) { + set_x(e); + } else { + set_number(e, div(get_number(ARG1), r)); + } + break; + case OP_REM: + // rem(v1,v2) = if v2 >= 0 then mod(v1,v2) else -mod(v1,v2) + SASSERT(arity == 2); + r = get_number(ARG2); + if (r.is_zero()) { + set_x(e); + } else { + r2 = mod(get_number(ARG1), r); + if (r.is_neg()) { r2.neg(); } + set_number(e, r2); + } + break; + case OP_MOD: + SASSERT(arity == 2); + r = get_number(ARG2); + if (r.is_zero()) { + set_x(e); + } else { + set_number(e, mod(get_number(ARG1), r)); + } + break; + case OP_TO_REAL: + SASSERT(arity == 1); + set_number(e, get_number(ARG1)); + break; + case OP_TO_INT: + SASSERT(arity == 1); + set_number(e, floor(get_number(ARG1))); + break; + case OP_IS_INT: + SASSERT(arity == 1); + set_bool(e, get_number(ARG1).is_int()); + break; + case OP_POWER: + set_x(e); + break; + default: + IF_VERBOSE(0, verbose_stream() << "Term not handled " << mk_pp(e, m) << "\n";); + UNREACHABLE(); + break; + } +} + +void model_evaluator::inherit_value(expr* e, expr* v) +{ + expr* w; + SASSERT(!is_unknown(v)); + SASSERT(m.get_sort(e) == m.get_sort(v)); + if (is_x(v)) { + set_x(e); + } else if (m.is_bool(e)) { + SASSERT(m.is_bool(v)); + if (is_true(v)) { set_true(e); } + else if (is_false(v)) { set_false(e); } + else { + TRACE("old_spacer", tout << "not inherited:\n" << mk_pp(e, m) << "\n" << mk_pp(v, m) << "\n";); + set_x(e); + } + } else if (m_arith.is_int_real(e)) { + set_number(e, get_number(v)); + } else if (m.is_value(v)) { + set_value(e, v); + } else if (m_values.find(v, w)) { + set_value(e, w); + } else { + TRACE("old_spacer", tout << "not inherited:\n" << mk_pp(e, m) << "\n" << mk_pp(v, m) << "\n";); + set_x(e); + } +} + +void model_evaluator::eval_exprs(expr_ref_vector& es) +{ + model_ref mr(m_model); + for (unsigned j = 0; j < es.size(); ++j) { + if (m_array.is_as_array(es[j].get())) { + es[j] = eval(mr, es[j].get()); + } + } +} + +bool model_evaluator::extract_array_func_interp(expr* a, vector& stores, expr_ref& else_case) +{ + SASSERT(m_array.is_array(a)); + + TRACE("old_spacer", tout << mk_pp(a, m) << "\n";); + while (m_array.is_store(a)) { + expr_ref_vector store(m); + store.append(to_app(a)->get_num_args() - 1, to_app(a)->get_args() + 1); + eval_exprs(store); + stores.push_back(store); + a = to_app(a)->get_arg(0); + } + + if (m_array.is_const(a)) { + else_case = to_app(a)->get_arg(0); + return true; + } + + while (m_array.is_as_array(a)) { + func_decl* f = m_array.get_as_array_func_decl(to_app(a)); + func_interp* g = m_model->get_func_interp(f); + unsigned sz = g->num_entries(); + unsigned arity = f->get_arity(); + for (unsigned i = 0; i < sz; ++i) { + expr_ref_vector store(m); + func_entry const* fe = g->get_entry(i); + store.append(arity, fe->get_args()); + store.push_back(fe->get_result()); + for (unsigned j = 0; j < store.size(); ++j) { + if (!is_ground(store[j].get())) { + TRACE("old_spacer", tout << "could not extract array interpretation: " << mk_pp(a, m) << "\n" << mk_pp(store[j].get(), m) << "\n";); + return false; + } + } + eval_exprs(store); + stores.push_back(store); + } + else_case = g->get_else(); + if (!else_case) { + TRACE("old_spacer", tout << "no else case " << mk_pp(a, m) << "\n";); + return false; + } + if (!is_ground(else_case)) { + TRACE("old_spacer", tout << "non-ground else case " << mk_pp(a, m) << "\n" << mk_pp(else_case, m) << "\n";); + return false; + } + if (m_array.is_as_array(else_case)) { + model_ref mr(m_model); + else_case = eval(mr, else_case); + } + TRACE("old_spacer", tout << "else case: " << mk_pp(else_case, m) << "\n";); + return true; + } + TRACE("old_spacer", tout << "no translation: " << mk_pp(a, m) << "\n";); + + return false; +} + +/** + best effort evaluator of extensional array equality. +*/ +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); + if (v1 == v2) { + set_true(e); + return; + } + sort* s = m.get_sort(arg1); + sort* r = get_array_range(s); + // give up evaluating finite domain/range arrays + if (!r->is_infinite() && !r->is_very_big() && !s->is_infinite() && !s->is_very_big()) { + TRACE("old_spacer", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + set_x(e); + return; + } + vector store; + expr_ref else1(m), else2(m); + if (!extract_array_func_interp(v1, store, else1) || + !extract_array_func_interp(v2, store, else2)) { + TRACE("old_spacer", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + set_x(e); + return; + } + + if (else1 != else2) { + if (m.is_value(else1) && m.is_value(else2)) { + TRACE("old_spacer", tout + << "defaults are different: " << mk_pp(e, m) << " " + << mk_pp(else1, m) << " " << mk_pp(else2, m) << "\n";); + set_false(e); + } else if (m_array.is_array(else1)) { + eval_array_eq(e, else1, else2); + } else { + TRACE("old_spacer", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + set_x(e); + } + return; + } + + expr_ref s1(m), s2(m), w1(m), w2(m); + expr_ref_vector args1(m), args2(m); + args1.push_back(v1); + args2.push_back(v2); + for (unsigned i = 0; i < store.size(); ++i) { + args1.resize(1); + args2.resize(1); + args1.append(store[i].size() - 1, store[i].c_ptr()); + 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); + if (w1 == w2) { + continue; + } + if (m.is_value(w1) && m.is_value(w2)) { + TRACE("old_spacer", tout << "Equality evaluation: " << mk_pp(e, m) << "\n"; + tout << mk_pp(s1, m) << " |-> " << mk_pp(w1, m) << "\n"; + tout << mk_pp(s2, m) << " |-> " << mk_pp(w2, m) << "\n";); + set_false(e); + } else if (m_array.is_array(w1)) { + eval_array_eq(e, w1, w2); + if (is_true(e)) { + continue; + } + } else { + TRACE("old_spacer", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + set_x(e); + } + return; + } + set_true(e); +} + +void model_evaluator::eval_eq(app* e, expr* arg1, expr* arg2) +{ + if (arg1 == arg2) { + set_true(e); + } else if (m_array.is_array(arg1)) { + eval_array_eq(e, arg1, arg2); + } else if (is_x(arg1) || is_x(arg2)) { + set_x(e); + } else if (m.is_bool(arg1)) { + bool val = is_true(arg1) == is_true(arg2); + SASSERT(val == (is_false(arg1) == is_false(arg2))); + if (val) { + set_true(e); + } else { + set_false(e); + } + } else if (m_arith.is_int_real(arg1)) { + set_bool(e, get_number(arg1) == get_number(arg2)); + } else { + expr* e1 = get_value(arg1); + expr* e2 = get_value(arg2); + if (m.is_value(e1) && m.is_value(e2)) { + set_bool(e, e1 == e2); + } else if (e1 == e2) { + set_bool(e, true); + } else { + TRACE("old_spacer", tout << "not value equal:\n" << mk_pp(e1, m) << "\n" << mk_pp(e2, m) << "\n";); + set_x(e); + } + } +} + +void model_evaluator::eval_basic(app* e) +{ + expr* arg1, *arg2; + expr *argCond, *argThen, *argElse, *arg; + bool has_x = false; + unsigned arity = e->get_num_args(); + switch (e->get_decl_kind()) { + case OP_AND: + for (unsigned j = 0; j < arity; ++j) { + expr * arg = e->get_arg(j); + if (is_false(arg)) { + set_false(e); + return; + } else if (is_x(arg)) { + has_x = true; + } else { + SASSERT(is_true(arg)); + } + } + if (has_x) { + set_x(e); + } else { + set_true(e); + } + break; + case OP_OR: + for (unsigned j = 0; j < arity; ++j) { + expr * arg = e->get_arg(j); + if (is_true(arg)) { + set_true(e); + return; + } else if (is_x(arg)) { + has_x = true; + } else { + SASSERT(is_false(arg)); + } + } + if (has_x) { + set_x(e); + } else { + set_false(e); + } + break; + case OP_NOT: + VERIFY(m.is_not(e, arg)); + if (is_true(arg)) { + set_false(e); + } else if (is_false(arg)) { + set_true(e); + } else { + SASSERT(is_x(arg)); + set_x(e); + } + break; + case OP_IMPLIES: + VERIFY(m.is_implies(e, arg1, arg2)); + if (is_false(arg1) || is_true(arg2)) { + set_true(e); + } else if (arg1 == arg2) { + set_true(e); + } else if (is_true(arg1) && is_false(arg2)) { + set_false(e); + } else { + SASSERT(is_x(arg1) || is_x(arg2)); + 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); + if (is_false(e)) { set_true(e); } + else if (is_true(e)) { set_false(e); } + break; + case OP_ITE: + VERIFY(m.is_ite(e, argCond, argThen, argElse)); + if (is_true(argCond)) { + inherit_value(e, argThen); + } else if (is_false(argCond)) { + inherit_value(e, argElse); + } else if (argThen == argElse) { + inherit_value(e, argThen); + } else if (m.is_bool(e)) { + SASSERT(is_x(argCond)); + if (is_x(argThen) || is_x(argElse)) { + set_x(e); + } else if (is_true(argThen) == is_true(argElse)) { + inherit_value(e, argThen); + } else { + set_x(e); + } + } else { + set_x(e); + } + break; + case OP_TRUE: + set_true(e); + break; + case OP_FALSE: + set_false(e); + break; + case OP_EQ: + VERIFY(m.is_eq(e, arg1, arg2)); + eval_eq(e, arg1, arg2); + break; + case OP_DISTINCT: { + vector values; + for (unsigned i = 0; i < arity; ++i) { + expr* arg = e->get_arg(i); + if (is_x(arg)) { + set_x(e); + return; + } + values.push_back(get_number(arg)); + } + std::sort(values.begin(), values.end()); + for (unsigned i = 0; i + 1 < values.size(); ++i) { + if (values[i] == values[i + 1]) { + set_false(e); + return; + } + } + set_true(e); + break; + } + default: + IF_VERBOSE(0, verbose_stream() << "Term not handled " << mk_pp(e, m) << "\n";); + UNREACHABLE(); + } +} + +void model_evaluator::eval_fmls(ptr_vector const& formulas) +{ + ptr_vector todo(formulas); + + while (!todo.empty()) { + expr * curr_e = todo.back(); + + if (!is_app(curr_e)) { + todo.pop_back(); + continue; + } + app * curr = to_app(curr_e); + + if (!is_unknown(curr)) { + todo.pop_back(); + continue; + } + unsigned arity = curr->get_num_args(); + for (unsigned i = 0; i < arity; ++i) { + if (is_unknown(curr->get_arg(i))) { + todo.push_back(curr->get_arg(i)); + } + } + if (todo.back() != curr) { + continue; + } + todo.pop_back(); + if (curr->get_family_id() == m_arith.get_family_id()) { + eval_arith(curr); + } else if (curr->get_family_id() == m.get_basic_family_id()) { + eval_basic(curr); + } else { + expr_ref vl(m); + m_model->eval(curr, vl); + assign_value(curr, vl); + } + + IF_VERBOSE(35, verbose_stream() << "assigned " << mk_pp(curr_e, m) + << (is_true(curr_e) ? "true" : is_false(curr_e) ? "false" : "unknown") << "\n";); + SASSERT(!is_unknown(curr)); + } +} + +bool model_evaluator::check_model(ptr_vector const& formulas) +{ + eval_fmls(formulas); + bool has_x = false; + for (unsigned i = 0; i < formulas.size(); ++i) { + expr * form = formulas[i]; + SASSERT(!is_unknown(form)); + TRACE("spacer_verbose", + tout << "formula is " << (is_true(form) ? "true" : is_false(form) ? "false" : "unknown") << "\n" << mk_pp(form, m) << "\n";); + + if (is_false(form)) { + IF_VERBOSE(0, verbose_stream() << "formula false in model: " << mk_pp(form, m) << "\n";); + UNREACHABLE(); + } + if (is_x(form)) { + IF_VERBOSE(0, verbose_stream() << "formula undetermined in model: " << mk_pp(form, m) << "\n";); + TRACE("old_spacer", model_smt2_pp(tout, m, *m_model, 0);); + has_x = true; + } + } + return !has_x; +} + +expr_ref model_evaluator::eval_heavy(const model_ref& model, expr* fml) +{ + expr_ref result(model->get_manager()); + + setup_model(model); + ptr_vector fmls; + fmls.push_back(fml); + eval_fmls(fmls); + if (is_false(fml)) { + result = m.mk_false(); + } else if (is_true(fml) || is_x(fml)) { + result = m.mk_true(); + } else if (m_arith.is_int_real(fml)) { + result = m_arith.mk_numeral(get_number(fml), m_arith.is_int(fml)); + } else { + result = get_value(fml); + } + reset(); + + return result; +} + +expr_ref model_evaluator::eval(const model_ref& model, func_decl* d) +{ + SASSERT(d->get_arity() == 0); + expr_ref result(m); + if (m_array.is_array(d->get_range())) { + expr_ref e(m); + e = m.mk_const(d); + result = eval(model, e); + } else { + result = model->get_const_interp(d); + } + return result; +} + +expr_ref model_evaluator::eval(const model_ref& model, expr* e) +{ + expr_ref result(m); + m_model = model.get(); + VERIFY(m_model->eval(e, result, true)); + if (m_array.is_array(e)) { + vector stores; + expr_ref_vector args(m); + expr_ref else_case(m); + if (extract_array_func_interp(result, stores, else_case)) { + result = m_array.mk_const_array(m.get_sort(e), else_case); + while (!stores.empty() && stores.back().back() == else_case) { + stores.pop_back(); + } + for (unsigned i = stores.size(); i > 0;) { + --i; + args.resize(1); + args[0] = result; + args.append(stores[i]); + result = m_array.mk_store(args.size(), args.c_ptr()); + } + return result; + } + } + return result; +} + + +} diff --git a/src/muz/spacer/spacer_legacy_mev.h b/src/muz/spacer/spacer_legacy_mev.h new file mode 100644 index 000000000..21790f022 --- /dev/null +++ b/src/muz/spacer/spacer_legacy_mev.h @@ -0,0 +1,117 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + + Deprecated implementation of model evaluator. To be removed. +*/ +#ifndef OLD_MEV_H +#define OLD_MEV_H + +#include "ast.h" +#include "ast_pp.h" +#include "obj_hashtable.h" +#include "ref_vector.h" +#include "simplifier.h" +#include "trace.h" +#include "vector.h" +#include "arith_decl_plugin.h" +#include "array_decl_plugin.h" +#include "bv_decl_plugin.h" + +namespace old { +class model_evaluator { + ast_manager& m; + arith_util m_arith; + array_util m_array; + obj_map m_numbers; + expr_ref_vector m_refs; + obj_map m_values; + model_ref m_model; + + //00 -- non-visited + //01 -- X + //10 -- false + //11 -- true + expr_mark m1; + expr_mark m2; + + /// used by collect() + expr_mark m_visited; + + + + void reset(); + + /// caches the values of all constants in the given model + void setup_model(const model_ref& model); + /// caches the value of an expression + void assign_value(expr* e, expr* v); + + /// extracts an implicant of the conjunction of formulas + void collect(ptr_vector const& formulas, ptr_vector& tocollect); + + /// one-round of extracting an implicant of e. The implicant + /// literals are stored in tocollect. The worklist is stored in todo + void process_formula(app* e, ptr_vector& todo, ptr_vector& tocollect); + void eval_arith(app* e); + void eval_basic(app* e); + void eval_eq(app* e, expr* arg1, expr* arg2); + void eval_array_eq(app* e, expr* arg1, expr* arg2); + void inherit_value(expr* e, expr* v); + + bool is_unknown(expr* x) { return !m1.is_marked(x) && !m2.is_marked(x); } + void set_unknown(expr* x) { m1.mark(x, false); m2.mark(x, false); } + bool is_x(expr* x) { return !m1.is_marked(x) && m2.is_marked(x); } + bool is_false(expr* x) { return m1.is_marked(x) && !m2.is_marked(x); } + bool is_true(expr* x) { return m1.is_marked(x) && m2.is_marked(x); } + void set_x(expr* x) { SASSERT(is_unknown(x)); m2.mark(x); } + void set_v(expr* x) { SASSERT(is_unknown(x)); m1.mark(x); } + void set_false(expr* x) { SASSERT(is_unknown(x)); m1.mark(x); } + void set_true(expr* x) { SASSERT(is_unknown(x)); m1.mark(x); m2.mark(x); } + void set_bool(expr* x, bool v) { if(v) { set_true(x); } else { set_false(x); } } + rational const& get_number(expr* x) const { return m_numbers.find(x); } + void set_number(expr* x, rational const& v) + { + set_v(x); + m_numbers.insert(x, v); + TRACE("spacer_verbose", tout << mk_pp(x, m) << " " << v << "\n";); + } + expr* get_value(expr* x) { return m_values.find(x); } + void set_value(expr* x, expr* v) + { set_v(x); m_refs.push_back(v); m_values.insert(x, v); } + + + /// evaluates all sub-formulas and terms of the input in the current model. + /// Caches the result + void eval_fmls(ptr_vector const & formulas); + + /// calls eval_fmls(). Then checks whether all formulas are + /// TRUE. Returns false if at lest one formula is unknown (X) + bool check_model(ptr_vector const & formulas); + + bool extract_array_func_interp(expr* a, vector& stores, + expr_ref& else_case); + + void eval_exprs(expr_ref_vector& es); + +public: + model_evaluator(ast_manager& m) : m(m), m_arith(m), m_array(m), m_refs(m) {} + + + /** + \brief extract literals from formulas that satisfy formulas. + + \pre model satisfies formulas + */ + void minimize_literals(ptr_vector const & formulas, const model_ref& mdl, + expr_ref_vector& result); + + expr_ref eval_heavy(const model_ref& mdl, expr* fml); + + expr_ref eval(const model_ref& mdl, expr* e); + expr_ref eval(const model_ref& mdl, func_decl* d); +}; +} + + + +#endif /* OLD_MEV_H */ diff --git a/src/muz/spacer/spacer_manager.cpp b/src/muz/spacer/spacer_manager.cpp new file mode 100644 index 000000000..077874488 --- /dev/null +++ b/src/muz/spacer/spacer_manager.cpp @@ -0,0 +1,386 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_manager.cpp + +Abstract: + + A manager class for SPACER, taking care of creating of AST + objects and conversions between them. + +Author: + + Krystof Hoder (t-khoder) 2011-8-25. + +Revision History: + +--*/ + +#include +#include "spacer_manager.h" +#include "ast_smt2_pp.h" +#include "for_each_expr.h" +#include "has_free_vars.h" +#include "expr_replacer.h" +#include "expr_abstract.h" +#include "model2expr.h" +#include "model_smt2_pp.h" +#include "model_converter.h" + +namespace spacer { + +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("spacer", model_smt2_pp(tout, m, *md, 0);); + apply(const_cast(m_mc), md, 0); +} + +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"; + } +} + +std::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; + + 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; + } +} + +/** + * Create a new skolem constant + */ +app* mk_zk_const(ast_manager &m, unsigned idx, sort *s) { + std::stringstream name; + name << "sk!" << idx; + return m.mk_const(symbol(name.str().c_str()), s); +} + +namespace find_zk_const_ns { +struct proc { + app_ref_vector &m_out; + proc (app_ref_vector &out) : 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() (quantifier const *n) const {} +}; +} + +void find_zk_const(expr *e, app_ref_vector &res) { + find_zk_const_ns::proc p(res); + for_each_expr (p, e); +} + +namespace has_zk_const_ns { +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) { + throw found(); + } + } + void operator() (quantifier const *n) const {} +}; +} + + +bool has_zk_const(expr *e){ + has_zk_const_ns::proc p; + try { + for_each_expr(p, e); + } + catch (has_zk_const_ns::found) { + return true; + } + return false; +} + +} diff --git a/src/muz/spacer/spacer_manager.h b/src/muz/spacer/spacer_manager.h new file mode 100644 index 000000000..360c4ceb2 --- /dev/null +++ b/src/muz/spacer/spacer_manager.h @@ -0,0 +1,345 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_manager.h + +Abstract: + + A manager class for SPACER, taking care of creating of AST + objects and conversions between them. + +Author: + + Krystof Hoder (t-khoder) 2011-8-25. + +Revision History: + +--*/ + +#ifndef _SPACER_MANAGER_H_ +#define _SPACER_MANAGER_H_ + +#include +#include +#include "bool_rewriter.h" +#include "expr_replacer.h" +#include "expr_substitution.h" +#include "map.h" +#include "ref_vector.h" +#include "smt_kernel.h" +#include "spacer_util.h" +#include "spacer_sym_mux.h" +#include "spacer_farkas_learner.h" +#include "spacer_smt_context_manager.h" +#include "dl_rule.h" +#include + +namespace smt { +class context; +} + +namespace spacer { + +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; + + mutable bool_rewriter m_brwr; + + 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); + + 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); + + //"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()); + } + + func_decl * o2n(func_decl * p, unsigned o_idx) const + { + return m_mux.conv(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)); + } + func_decl * n2o(func_decl * p, unsigned o_idx) const + { + 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) const + { 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) const + { m_mux.conv_formula(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); } + + 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); } + + /** + 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(); + } +}; + +app* mk_zk_const (ast_manager &m, unsigned idx, sort *s); +void find_zk_const(expr* e, app_ref_vector &out); +bool has_zk_const(expr* e); +} + +#endif diff --git a/src/muz/spacer/spacer_marshal.cpp b/src/muz/spacer/spacer_marshal.cpp new file mode 100644 index 000000000..fce0d6a34 --- /dev/null +++ b/src/muz/spacer/spacer_marshal.cpp @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel +Module Name: + + spacer_marshal.cpp + +Abstract: + + marshaling and unmarshaling of expressions + + --*/ +#include "spacer_marshal.h" + +#include +#include "cmd_context.h" +#include "smt2parser.h" +#include "vector.h" +#include "ast_smt_pp.h" +#include "ast_pp.h" + +namespace spacer { +std::ostream &marshal(std::ostream &os, expr_ref e, ast_manager &m) +{ + ast_smt_pp pp(m); + pp.display_smt2(os, e); + return os; +} + +std::string marshal(expr_ref e, ast_manager &m) +{ + std::stringstream ss; + marshal(ss, e, m); + return ss.str(); +} + + +expr_ref unmarshal(std::istream &is, ast_manager &m) +{ + cmd_context ctx(false, &m); + ctx.set_ignore_check(true); + if (!parse_smt2_commands(ctx, is)) { return expr_ref(0, m); } + + ptr_vector::const_iterator it = ctx.begin_assertions(); + ptr_vector::const_iterator end = ctx.end_assertions(); + if (it == end) { return expr_ref(m.mk_true(), m); } + unsigned size = static_cast(end - it); + return expr_ref(m.mk_and(size, it), m); +} + +expr_ref unmarshal(std::string s, ast_manager &m) +{ + std::istringstream is(s); + return unmarshal(is, m); +} +} diff --git a/src/muz/spacer/spacer_marshal.h b/src/muz/spacer/spacer_marshal.h new file mode 100644 index 000000000..e4c59628f --- /dev/null +++ b/src/muz/spacer/spacer_marshal.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel +Module Name: + + spacer_marshal.h + +Abstract: + + marshaling and unmarshaling of expressions + + --*/ +#ifndef _SPACER_MARSHAL_H_ +#define _SPACER_MARSHAL_H_ + +#include +#include "ast.h" +#include + +namespace spacer { +std::ostream &marshal(std::ostream &os, expr_ref e, ast_manager &m); +std::string marshal(expr_ref e, ast_manager &m); +expr_ref unmarshal(std::string s, ast_manager &m); +expr_ref unmarshal(std::istream &is, ast_manager &m); + + +} + + +#endif diff --git a/src/muz/spacer/spacer_matrix.cpp b/src/muz/spacer/spacer_matrix.cpp new file mode 100644 index 000000000..b7d03d453 --- /dev/null +++ b/src/muz/spacer/spacer_matrix.cpp @@ -0,0 +1,159 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_matrix.cpp + +Abstract: + a matrix + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#include "spacer_matrix.h" + +namespace spacer +{ + spacer_matrix::spacer_matrix(unsigned m, unsigned n) : m_num_rows(m), m_num_cols(n) + { + for (unsigned i=0; i < m; ++i) + { + vector v; + for (unsigned j=0; j < n; ++j) + { + v.push_back(rational(0)); + } + m_matrix.push_back(v); + } + } + + unsigned spacer_matrix::num_rows() + { + return m_num_rows; + } + + unsigned spacer_matrix::num_cols() + { + return m_num_cols; + } + + rational spacer_matrix::get(unsigned int i, unsigned int j) + { + SASSERT(i < m_num_rows); + SASSERT(j < m_num_cols); + + return m_matrix[i][j]; + } + + void spacer_matrix::set(unsigned int i, unsigned int j, rational v) + { + SASSERT(i < m_num_rows); + SASSERT(j < m_num_cols); + + m_matrix[i][j] = v; + } + + unsigned spacer_matrix::perform_gaussian_elimination() + { + unsigned i=0; + unsigned j=0; + while(i < m_matrix.size() && j < m_matrix[0].size()) + { + // find maximal element in column with row index bigger or equal i + rational max = m_matrix[i][j]; + unsigned max_index = i; + + for (unsigned k=i+1; k < m_matrix.size(); ++k) + { + if (max < m_matrix[k][j]) + { + max = m_matrix[k][j]; + max_index = k; + } + } + + if (max.is_zero()) // skip this column + { + ++j; + } + else + { + // reorder rows if necessary + vector tmp = m_matrix[i]; + m_matrix[i] = m_matrix[max_index]; + m_matrix[max_index] = m_matrix[i]; + + // normalize row + rational pivot = m_matrix[i][j]; + if (!pivot.is_one()) + { + for (unsigned k=0; k < m_matrix[i].size(); ++k) + { + m_matrix[i][k] = m_matrix[i][k] / pivot; + } + } + + // subtract row from all other rows + for (unsigned k=1; k < m_matrix.size(); ++k) + { + if (k != i) + { + rational factor = m_matrix[k][j]; + for (unsigned l=0; l < m_matrix[k].size(); ++l) + { + m_matrix[k][l] = m_matrix[k][l] - (factor * m_matrix[i][l]); + } + } + } + + ++i; + ++j; + } + } + + if (get_verbosity_level() >= 1) + { + SASSERT(m_matrix.size() > 0); + } + + return i; //i points to the row after the last row which is non-zero + } + + void spacer_matrix::print_matrix() + { + verbose_stream() << "\nMatrix\n"; + for (const auto& row : m_matrix) + { + for (const auto& element : row) + { + verbose_stream() << element << ", "; + } + verbose_stream() << "\n"; + } + verbose_stream() << "\n"; + } + void spacer_matrix::normalize() + { + rational den = rational::one(); + for (unsigned i=0; i < m_num_rows; ++i) + { + for (unsigned j=0; j < m_num_cols; ++j) + { + den = lcm(den, denominator(m_matrix[i][j])); + } + } + for (unsigned i=0; i < m_num_rows; ++i) + { + for (unsigned j=0; j < m_num_cols; ++j) + { + m_matrix[i][j] = den * m_matrix[i][j]; + SASSERT(m_matrix[i][j].is_int()); + } + } + } +} diff --git a/src/muz/spacer/spacer_matrix.h b/src/muz/spacer/spacer_matrix.h new file mode 100644 index 000000000..def194793 --- /dev/null +++ b/src/muz/spacer/spacer_matrix.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_matrix.h + +Abstract: + a matrix + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#ifndef _SPACER_MATRIX_H_ +#define _SPACER_MATRIX_H_ + +#include "ast.h" + +namespace spacer { + + class spacer_matrix { + public: + spacer_matrix(unsigned m, unsigned n); // m rows, n colums + + unsigned num_rows(); + unsigned num_cols(); + + rational get(unsigned i, unsigned j); + void set(unsigned i, unsigned j, rational v); + + unsigned perform_gaussian_elimination(); + + void print_matrix(); + void normalize(); + private: + unsigned m_num_rows; + unsigned m_num_cols; + vector> m_matrix; + }; +} + +#endif diff --git a/src/muz/spacer/spacer_mev_array.cpp b/src/muz/spacer/spacer_mev_array.cpp new file mode 100644 index 000000000..7a69eeab5 --- /dev/null +++ b/src/muz/spacer/spacer_mev_array.cpp @@ -0,0 +1,217 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_mev_array.cpp + +Abstract: + + Evaluate array expressions in a given model. + +Author: + +Revision History: + +--*/ +#include"model.h" +#include"model_evaluator_params.hpp" +#include"rewriter_types.h" +#include"model_evaluator.h" +#include"spacer_mev_array.h" +#include"bool_rewriter.h" +#include"arith_rewriter.h" +#include"bv_rewriter.h" +#include"datatype_rewriter.h" +#include"array_rewriter.h" +#include"rewriter_def.h" +#include"cooperate.h" +#include"ast_pp.h" +#include"func_interp.h" + + + +// model_evaluator_array_util + + +void model_evaluator_array_util::eval_exprs(model& mdl, expr_ref_vector& es) { + for (unsigned j = 0; j < es.size(); ++j) { + if (m_array.is_as_array(es.get (j))) { + expr_ref r (m); + eval(mdl, es.get (j), r); + es.set (j, r); + } + } +} + +bool model_evaluator_array_util::extract_array_func_interp(model& mdl, expr* a, vector& stores, expr_ref& else_case) { + SASSERT(m_array.is_array(a)); + + TRACE("model_evaluator", tout << mk_pp(a, m) << "\n";); + while (m_array.is_store(a)) { + expr_ref_vector store(m); + store.append(to_app(a)->get_num_args()-1, to_app(a)->get_args()+1); + eval_exprs(mdl, store); + stores.push_back(store); + a = to_app(a)->get_arg(0); + } + + if (m_array.is_const(a)) { + else_case = to_app(a)->get_arg(0); + return true; + } + + while (m_array.is_as_array(a)) { + func_decl* f = m_array.get_as_array_func_decl(to_app(a)); + func_interp* g = mdl.get_func_interp(f); + unsigned sz = g->num_entries(); + unsigned arity = f->get_arity(); + for (unsigned i = 0; i < sz; ++i) { + expr_ref_vector store(m); + func_entry const* fe = g->get_entry(i); + store.append(arity, fe->get_args()); + store.push_back(fe->get_result()); + for (unsigned j = 0; j < store.size(); ++j) { + if (!is_ground(store[j].get())) { + TRACE("model_evaluator", tout << "could not extract array interpretation: " << mk_pp(a, m) << "\n" << mk_pp(store[j].get(), m) << "\n";); + return false; + } + } + eval_exprs(mdl, store); + stores.push_back(store); + } + else_case = g->get_else(); + if (!else_case) { + TRACE("model_evaluator", tout << "no else case " << mk_pp(a, m) << "\n";); + return false; + } + if (!is_ground(else_case)) { + TRACE("model_evaluator", tout << "non-ground else case " << mk_pp(a, m) << "\n" << mk_pp(else_case, m) << "\n";); + return false; + } + if (m_array.is_as_array(else_case)) { + expr_ref r (m); + eval(mdl, else_case, r); + else_case = r; + } + TRACE("model_evaluator", tout << "else case: " << mk_pp(else_case, m) << "\n";); + return true; + } + TRACE("model_evaluator", tout << "no translation: " << mk_pp(a, m) << "\n";); + + return false; +} + +void model_evaluator_array_util::eval_array_eq(model& mdl, app* e, expr* arg1, expr* arg2, expr_ref& res) { + TRACE("model_evaluator", tout << "array equality: " << mk_pp(e, m) << "\n";); + expr_ref v1(m), v2(m); + eval (mdl, arg1, v1); + eval (mdl, arg2, v2); + if (v1 == v2) { + res = m.mk_true (); + return; + } + sort* s = m.get_sort(arg1); + sort* r = get_array_range(s); + // give up evaluating finite domain/range arrays + if (!r->is_infinite() && !r->is_very_big() && !s->is_infinite() && !s->is_very_big()) { + TRACE("model_evaluator", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + res.reset (); + return; + } + vector store; + expr_ref else1(m), else2(m); + if (!extract_array_func_interp(mdl, v1, store, else1) || + !extract_array_func_interp(mdl, v2, store, else2)) { + TRACE("model_evaluator", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + res.reset (); + return; + } + + if (else1 != else2) { + if (m.is_value(else1) && m.is_value(else2)) { + TRACE("model_evaluator", tout + << "defaults are different: " << mk_pp(e, m) << " " + << mk_pp(else1, m) << " " << mk_pp(else2, m) << "\n";); + res = m.mk_false (); + } + else if (m_array.is_array(else1)) { + eval_array_eq(mdl, e, else1, else2, res); + } + else { + TRACE("model_evaluator", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + res.reset (); + } + return; + } + + expr_ref s1(m), s2(m), w1(m), w2(m); + expr_ref_vector args1(m), args2(m); + args1.push_back(v1); + args2.push_back(v2); + for (unsigned i = 0; i < store.size(); ++i) { + args1.resize(1); + args2.resize(1); + args1.append(store[i].size()-1, store[i].c_ptr()); + 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()); + eval (mdl, s1, w1); + eval (mdl, s2, w2); + if (w1 == w2) { + continue; + } + if (m.is_value(w1) && m.is_value(w2)) { + TRACE("model_evaluator", tout << "Equality evaluation: " << mk_pp(e, m) << "\n"; + tout << mk_pp(s1, m) << " |-> " << mk_pp(w1, m) << "\n"; + tout << mk_pp(s2, m) << " |-> " << mk_pp(w2, m) << "\n";); + res = m.mk_false (); + } + else if (m_array.is_array(w1)) { + eval_array_eq(mdl, e, w1, w2, res); + if (m.is_true (res)) { + continue; + } + } + else { + TRACE("model_evaluator", tout << "equality is unknown: " << mk_pp(e, m) << "\n";); + res.reset (); + } + return; + } + res = m.mk_true (); +} + +void model_evaluator_array_util::eval(model& mdl, expr* e, expr_ref& r, bool model_completion) { + model_evaluator mev (mdl); + mev.set_model_completion (model_completion); + bool eval_result = true; + try { + mev (e, r); + } + catch (model_evaluator_exception &) { + eval_result = false; + } + VERIFY(eval_result); + + if (m_array.is_array(e)) { + vector stores; + expr_ref_vector args(m); + expr_ref else_case(m); + if (extract_array_func_interp(mdl, r, stores, else_case)) { + r = m_array.mk_const_array(m.get_sort(e), else_case); + while (!stores.empty() && stores.back().back() == else_case) { + stores.pop_back(); + } + for (unsigned i = stores.size(); i > 0; ) { + --i; + args.resize(1); + args[0] = r; + args.append(stores[i]); + r = m_array.mk_store(args.size(), args.c_ptr()); + } + return; + } + } + return; +} diff --git a/src/muz/spacer/spacer_mev_array.h b/src/muz/spacer/spacer_mev_array.h new file mode 100644 index 000000000..d7904d677 --- /dev/null +++ b/src/muz/spacer/spacer_mev_array.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_mev_array.h + +Abstract: + + Utilities to evaluate arrays in the model. + +Author: + based on model_evaluator in muz/pdr/pdr_util.h + +Revision History: + +--*/ +#ifndef _SPACER_MEV_ARRAY_H_ +#define _SPACER_MEV_ARRAY_H_ + +#include"ast.h" +#include"rewriter_types.h" +#include"params.h" +#include "array_decl_plugin.h" + +/** + * based on model_evaluator in muz/pdr/pdr_util.h + */ +class model_evaluator_array_util { + ast_manager& m; + array_util m_array; + + void eval_exprs(model& mdl, expr_ref_vector& es); + + bool extract_array_func_interp(model& mdl, expr* a, vector& stores, expr_ref& else_case); + +public: + + model_evaluator_array_util (ast_manager& m): + m (m), + m_array (m) + {} + + /** + * best effort evaluator of extensional array equality. + */ + void eval_array_eq(model& mdl, app* e, expr* arg1, expr* arg2, expr_ref& res); + + void eval(model& mdl, expr* e, expr_ref& r, bool model_completion = true); +}; + +#endif diff --git a/src/muz/spacer/spacer_min_cut.cpp b/src/muz/spacer/spacer_min_cut.cpp new file mode 100644 index 000000000..c56ddbd66 --- /dev/null +++ b/src/muz/spacer/spacer_min_cut.cpp @@ -0,0 +1,289 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_min_cut.cpp + +Abstract: + min cut solver + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#include "spacer_min_cut.h" + +namespace spacer { + + spacer_min_cut::spacer_min_cut() + { + m_n = 2; + + // push back two empty vectors for source and sink + m_edges.push_back(vector>()); + m_edges.push_back(vector>()); + } + + unsigned spacer_min_cut::new_node() + { + return m_n++; + } + + void spacer_min_cut::add_edge(unsigned int i, unsigned int j, unsigned int capacity) + { + if (i >= m_edges.size()) + { + m_edges.resize(i + 1); + } + m_edges[i].insert(std::make_pair(j, 1)); + STRACE("spacer.mincut", + verbose_stream() << "adding edge (" << i << "," << j << ")\n"; + ); + + } + + void spacer_min_cut::compute_min_cut(vector& cut_nodes) + { + if (m_n == 2) + { + return; + } + + m_d.resize(m_n); + m_pred.resize(m_n); + + // compute initial distances and number of nodes + compute_initial_distances(); + + unsigned i = 0; + + while (m_d[0] < m_n) + { + unsigned j = get_admissible_edge(i); + + if (j < m_n) + { + // advance(i) + m_pred[j] = i; + i = j; + + // if i is the sink, augment path + if (i == 1) + { + augment_path(); + i = 0; + } + } + else + { + // retreat + compute_distance(i); + if (i != 0) + { + i = m_pred[i]; + } + } + } + + // split nodes into reachable and unreachable ones + vector reachable(m_n); + compute_reachable_nodes(reachable); + + // find all edges between reachable and unreachable nodes and for each such edge, add corresponding lemma to unsat-core + compute_cut_and_add_lemmas(reachable, cut_nodes); + } + + void spacer_min_cut::compute_initial_distances() + { + vector todo; + vector visited(m_n); + + todo.push_back(0); // start at the source, since we do postorder traversel + + while (!todo.empty()) + { + unsigned current = todo.back(); + + // if we haven't already visited current + if (!visited[current]) { + bool existsUnvisitedParent = false; + + // add unprocessed parents to stack for DFS. If there is at least one unprocessed parent, don't compute the result + // for current now, but wait until those unprocessed parents are processed. + for (unsigned i = 0, sz = m_edges[current].size(); i < sz; ++i) + { + unsigned parent = m_edges[current][i].first; + + // if we haven't visited the current parent yet + if(!visited[parent]) + { + // add it to the stack + todo.push_back(parent); + existsUnvisitedParent = true; + } + } + + // if we already visited all parents, we can visit current too + if (!existsUnvisitedParent) { + visited[current] = true; + todo.pop_back(); + + compute_distance(current); // I.H. all parent distances are already computed + } + } + else { + todo.pop_back(); + } + } + } + + unsigned spacer_min_cut::get_admissible_edge(unsigned i) + { + for (const auto& pair : m_edges[i]) + { + if (pair.second > 0 && m_d[i] == m_d[pair.first] + 1) + { + return pair.first; + } + } + return m_n; // no element found + } + + void spacer_min_cut::augment_path() + { + // find bottleneck capacity + unsigned max = std::numeric_limits::max(); + unsigned k = 1; + while (k != 0) + { + unsigned l = m_pred[k]; + for (const auto& pair : m_edges[l]) + { + if (pair.first == k) + { + if (max > pair.second) + { + max = pair.second; + } + } + } + k = l; + } + + k = 1; + while (k != 0) + { + unsigned l = m_pred[k]; + + // decrease capacity + for (auto& pair : m_edges[l]) + { + if (pair.first == k) + { + pair.second -= max; + } + } + // increase reverse flow + bool already_exists = false; + for (auto& pair : m_edges[k]) + { + if (pair.first == l) + { + already_exists = true; + pair.second += max; + } + } + if (!already_exists) + { + m_edges[k].insert(std::make_pair(l, max)); + } + k = l; + } + } + + void spacer_min_cut::compute_distance(unsigned i) + { + if (i == 1) // sink node + { + m_d[1] = 0; + } + else + { + unsigned min = std::numeric_limits::max(); + + // find edge (i,j) with positive residual capacity and smallest distance + for (const auto& pair : m_edges[i]) + { + if (pair.second > 0) + { + unsigned tmp = m_d[pair.first] + 1; + if (tmp < min) + { + min = tmp; + } + } + } + m_d[i] = min; + } + } + + void spacer_min_cut::compute_reachable_nodes(vector& reachable) + { + vector todo; + + todo.push_back(0); + while (!todo.empty()) + { + unsigned current = todo.back(); + todo.pop_back(); + + if (!reachable[current]) + { + reachable[current] = true; + + for (const auto& pair : m_edges[current]) + { + if (pair.second > 0) + { + todo.push_back(pair.first); + } + } + } + } + } + + void spacer_min_cut::compute_cut_and_add_lemmas(vector& reachable, vector& cut_nodes) + { + vector todo; + vector visited(m_n); + + todo.push_back(0); + while (!todo.empty()) + { + unsigned current = todo.back(); + todo.pop_back(); + + if (!visited[current]) + { + visited[current] = true; + + for (const auto& pair : m_edges[current]) + { + unsigned successor = pair.first; + if (reachable[successor]) + { + todo.push_back(successor); + } + else + { + cut_nodes.push_back(successor); + } + } + } + } + } +} diff --git a/src/muz/spacer/spacer_min_cut.h b/src/muz/spacer/spacer_min_cut.h new file mode 100644 index 000000000..ae285b9bb --- /dev/null +++ b/src/muz/spacer/spacer_min_cut.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_min_cut.h + +Abstract: + min cut solver + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ + +#ifndef _SPACER_MIN_CUT_H_ +#define _SPACER_MIN_CUT_H_ + +#include "ast.h" + +namespace spacer { + + class spacer_min_cut { + public: + spacer_min_cut(); + + unsigned new_node(); + void add_edge(unsigned i, unsigned j, unsigned capacity); + void compute_min_cut(vector& cut_nodes); + + private: + + unsigned m_n; // number of vertices in the graph + + vector > > m_edges; // map from node to all outgoing edges together with their weights (also contains "reverse edges") + vector m_d; // approximation of distance from node to sink in residual graph + vector m_pred; // predecessor-information for reconstruction of augmenting path + vector m_node_to_formula; // maps each node to the corresponding formula in the original proof + + void compute_initial_distances(); + unsigned get_admissible_edge(unsigned i); + void augment_path(); + void compute_distance(unsigned i); + void compute_reachable_nodes(vector& reachable); + void compute_cut_and_add_lemmas(vector& reachable, vector& cut_nodes); + }; +} + +#endif diff --git a/src/muz/spacer/spacer_notes.txt b/src/muz/spacer/spacer_notes.txt new file mode 100644 index 000000000..4211bc1ce --- /dev/null +++ b/src/muz/spacer/spacer_notes.txt @@ -0,0 +1,231 @@ +a queue contains a model_node + +let n = leaves.pop_top () + +if (!n.has_derivation ()) + + if n.pt ().must_reach (n.post ()) + add parent of n to the leaves + return + + check abstract reachability of n + + if must reachable then + create new reachability fact for n.pt () + add parent of n to the leaves + else if may reachable then + create derivation d for n + create model_node kid for the top of d + add kid to the leaves + + else /* unreachable */ + create a lemma for n.pt () + p = parent of n + p.reset_derivation() + add p to the leaves + +else if (n.has_derivation ()) + + create next model_node kid for n.get_derivation () + + if (kid != NULL) + add kid to leaves + else /* done with the derivation, no more kids */ + // the derivation is reachable, otherwise it was reset in another branch + p = parent of n + p.reset_derivation () + add p to the leaves + + +================================================================================= +create derivation for the top of d +input: + model M, + transition relation formula trans with auxiliary variables quantified out + sequence of pedicates P_i, + may and must summaries of P_i +================================================================================= + +create first derivation child: + input: model + + +create next derivation child: + create new model + update trans by computing pre-image over new reachability facts + call create next derivation child + +private: +create next derivation child using a given model, and starting index + +========================================================= + +create a next model for a derivation + + +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// an obligation +model_node + // NULL means root + model_node_ref parent + model_node_ref_vector kids + + pred_transformer &predicate + expr* condition + unsigned level + unsigned depth + // monotonically increasing + unsigned id + + bool open; + + +model_node::close () + open = false + for k : kids do k.close () + +model_search + + model_node_ref root; + + // proof obligations + priority_queue m_obligations; + model_node_ref m_last_reachable; + + +bool model_node::operator< (model_node& other) + lexicographic order based on + level<, depth<, id> + + + +assert (!m_last_reachable); +while (!m_obligations.empty ()) +{ + // propagate reachability as much as possible + while (m_last_reachable) + { + obl = m_last_reachable + m_last_reachable.reset (); + if (is_root (obl)) return true; + if (discharge_obligation (obl.get_parent ()) == l_true) + m_last_reachable = obl.get_parent (); + } + + // at least one obligation is not closed, ow root was reachable + while (m_obligations.top ().is_closed ()) m_obligations.pop (); + assert (!m_obligations.empty ()); + + // process an obligation + assert (!m_last_reachable) + obl = m_obligations.top (); + switch (discharge_obligation (obl)) + { + case l_true: + // if reachable, schedule a reachability round + m_last_reachable = m_obligations.top (); + m_obligations.pop (); + break; + case l_false: + // if unreachable removed from the queue + m_obligations.pop (); + /// bump level + obl.inc_level (); + /// optionally insert back into the queue + if (is_active (obl)) m_obligations.push (obl); + break; + default: + assert (m_obligations.top () != obl); + } +} +return false + +/// with priority queue +bool is_active (model_node obl) { return level <= m_search.max_level (); } +/// with out priority queue. Discharged obligations are dropped +bool is_active (model_node obl) { return false; } + +discharge_obligation (model_node obl) +{ + assert (!obl.is_closed ()); + switch (check_reachability (obl)) + { + case l_true: + obl.close () + update reachability facts + return l_true; + case l_false: + update lemmas + return l_false + case l_unknown: + create children + populate m_obligations queue + return l_unknown + } +} + + +============================================================= + +a node keeps a derivation object + +if a node is sat, a new node is constructed and inherits the derivation object +if a node is sat and the derivation is done, this is reported to the parent + +expand_node(n): + process node ignoring derivation + if sat: + if concrete: + if has derivation and has next child + close current node and push new node + return l_undef + else + return l_true + else + create_child (creates a new node and optionally sets derivation) + else if unsat + generate lemmas + derivation remains unchanged to be used at a higher level + return +====================================================================== +1. open disjunction for transition relation + - a fresh literal to open the disjunction of the transition relation + - expr* expand_init (expr *e) -- add e to initial state and return + new disj var + - close the disjunction by passing the negation of the literal + during various calls + - store the literal negated to have access to both positive and + negative versions + - with this, can do an optional check whether the lemmas alone are + strong enough to discharge the counterexample. Easiest is to + implement it as a separate pre-check. + +2. auxiliary variables in lemmas and reach-facts. + - store and expect auxiliary variables + - quantify them out when necessary + +3. initial rules as reach-facts + - add initial rules of a predicate to its reach-facts. Propagate them to uses. + - this way, the checks at level 0 will include initial rules of + immediate predecessors +====================================================================== + +reach_fact_ref_vector m_reach_facts +app_ref_vector m_reach_case_vars + +bool is_must_reachable (expr *state, model_ref *model) +reach_fact* get_used_reach_fact (model_evaluator &mev) +app* mk_fresh_reach_case_var () +expr* get_reach () +expr* get_last_reach_case_var () +app* get_reach_case_var (unsigned idx) + +get_used_origin_reach_fact(): + + +====================================================================== +4. track relationship between an obligation and lemmas. Attempt to + generalize an obligation into the exact lemma that worked + before. Perhaps pick one lemma with highest level? Implement as + core-generalizer. Will require reworking how legacy_frames is implemented. diff --git a/src/muz/spacer/spacer_proof_utils.cpp b/src/muz/spacer/spacer_proof_utils.cpp new file mode 100644 index 000000000..ff020dee4 --- /dev/null +++ b/src/muz/spacer/spacer_proof_utils.cpp @@ -0,0 +1,332 @@ +/*++ +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 "spacer_proof_utils.h" +#include "ast_util.h" +#include "ast_pp.h" + +#include "proof_checker.h" + +namespace spacer { + +ProofIteratorPostOrder::ProofIteratorPostOrder(proof* root, ast_manager& manager) : m(manager) +{m_todo.push_back(root);} + +bool ProofIteratorPostOrder::hasNext() +{return !m_todo.empty();} + +/* + * iterative post-order depth-first search (DFS) through the proof DAG + */ +proof* ProofIteratorPostOrder::next() +{ + while (!m_todo.empty()) { + proof* currentNode = m_todo.back(); + + // if we haven't already visited the current unit + if (!m_visited.is_marked(currentNode)) { + bool existsUnvisitedParent = false; + + // add unprocessed premises to stack for DFS. If there is at least one unprocessed premise, don't compute the result + // for currentProof now, but wait until those unprocessed premises are processed. + 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)); + + // if we haven't visited the current premise yet + if (!m_visited.is_marked(premise)) { + // add it to the stack + m_todo.push_back(premise); + existsUnvisitedParent = true; + } + } + + // if we already visited all parent-inferences, we can visit the inference too + if (!existsUnvisitedParent) { + m_visited.mark(currentNode, true); + m_todo.pop_back(); + return currentNode; + } + } else { + m_todo.pop_back(); + } + } + // we have already iterated through all inferences + return NULL; +} + + +class reduce_hypotheses { + ast_manager &m; + // tracking all created expressions + expr_ref_vector m_pinned; + + // cache for the transformation + obj_map m_cache; + + // map from unit literals to their hypotheses-free derivations + obj_map m_units; + + // -- all hypotheses in the the proof + obj_hashtable m_hyps; + + // marks hypothetical proofs + ast_mark m_hypmark; + + + // stack + ptr_vector m_todo; + + void reset() + { + m_cache.reset(); + m_units.reset(); + m_hyps.reset(); + m_hypmark.reset(); + m_pinned.reset(); + } + + bool compute_mark1(proof *pr) + { + bool hyp_mark = false; + // lemmas clear all hypotheses + if (!m.is_lemma(pr)) { + for (unsigned i = 0, sz = m.get_num_parents(pr); i < sz; ++i) { + if (m_hypmark.is_marked(m.get_parent(pr, i))) { + hyp_mark = true; + break; + } + } + } + m_hypmark.mark(pr, hyp_mark); + return hyp_mark; + } + + void compute_marks(proof* pr) + { + proof *p; + ProofIteratorPostOrder pit(pr, m); + while (pit.hasNext()) { + p = pit.next(); + if (m.is_hypothesis(p)) { + m_hypmark.mark(p, true); + m_hyps.insert(m.get_fact(p)); + } else { + bool hyp_mark = compute_mark1(p); + // collect units that are hyp-free and are used as hypotheses somewhere + if (!hyp_mark && m.has_fact(p) && m_hyps.contains(m.get_fact(p))) + { m_units.insert(m.get_fact(p), p); } + } + } + } + void find_units(proof *pr) + { + // optional. not implemented yet. + } + + void reduce(proof* pf, proof_ref &out) + { + proof *res = NULL; + + m_todo.reset(); + m_todo.push_back(pf); + ptr_buffer args; + bool dirty = false; + + while (!m_todo.empty()) { + proof *p, *tmp, *pp; + unsigned todo_sz; + + p = m_todo.back(); + if (m_cache.find(p, tmp)) { + res = tmp; + m_todo.pop_back(); + continue; + } + + dirty = false; + args.reset(); + todo_sz = m_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 = dirty || pp != tmp; + } else { + m_todo.push_back(pp); + } + } + + if (todo_sz < m_todo.size()) { continue; } + else { m_todo.pop_back(); } + + if (m.is_hypothesis(p)) { + // hyp: replace by a corresponding unit + if (m_units.find(m.get_fact(p), tmp)) { + res = tmp; + } else { 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.get(0), m.get_fact(p)); + compute_mark1(res); + } else if (m.is_unit_resolution(p)) { + // unit: reduce untis; reduce the first premise; rebuild unit resolution + res = mk_unit_resolution_core(args.size(), args.c_ptr()); + compute_mark1(res); + } else { + // other: reduce all premises; reapply + if (m.has_fact(p)) { args.push_back(to_app(m.get_fact(p))); } + SASSERT(p->get_decl()->get_arity() == args.size()); + res = m.mk_app(p->get_decl(), args.size(), (expr * const*)args.c_ptr()); + m_pinned.push_back(res); + compute_mark1(res); + } + + SASSERT(res); + m_cache.insert(p, res); + + if (m.has_fact(res) && m.is_false(m.get_fact(res))) { break; } + } + + out = res; + } + + // returns true if (hypothesis (not a)) would be reduced + bool is_reduced(expr *a) + { + expr_ref e(m); + if (m.is_not(a)) { e = to_app(a)->get_arg(0); } + else { e = m.mk_not(a); } + + return m_units.contains(e); + } + proof *mk_lemma_core(proof *pf, expr *fact) + { + ptr_buffer args; + expr_ref lemma(m); + + if (m.is_or(fact)) { + for (unsigned i = 0, sz = to_app(fact)->get_num_args(); i < sz; ++i) { + expr *a = to_app(fact)->get_arg(i); + if (!is_reduced(a)) + { args.push_back(a); } + } + } else if (!is_reduced(fact)) + { args.push_back(fact); } + + + if (args.size() == 0) { return pf; } + else if (args.size() == 1) { + lemma = args.get(0); + } else { + lemma = m.mk_or(args.size(), args.c_ptr()); + } + proof* res = m.mk_lemma(pf, lemma); + m_pinned.push_back(res); + + if (m_hyps.contains(lemma)) + { m_units.insert(lemma, res); } + return res; + } + + proof *mk_unit_resolution_core(unsigned num_args, proof* const *args) + { + + ptr_buffer pf_args; + pf_args.push_back(args [0]); + + app *cls_fact = to_app(m.get_fact(args[0])); + ptr_buffer cls; + if (m.is_or(cls_fact)) { + for (unsigned i = 0, sz = cls_fact->get_num_args(); i < sz; ++i) + { cls.push_back(cls_fact->get_arg(i)); } + } else { cls.push_back(cls_fact); } + + // construct new resovent + ptr_buffer new_fact_cls; + bool found; + // XXX quadratic + for (unsigned i = 0, sz = cls.size(); i < sz; ++i) { + found = false; + for (unsigned j = 1; j < num_args; ++j) { + if (m.is_complement(cls.get(i), m.get_fact(args [j]))) { + found = true; + pf_args.push_back(args [j]); + break; + } + } + if (!found) { + new_fact_cls.push_back(cls.get(i)); + } + } + + SASSERT(new_fact_cls.size() + pf_args.size() - 1 == cls.size()); + expr_ref new_fact(m); + new_fact = mk_or(m, new_fact_cls.size(), new_fact_cls.c_ptr()); + + // create new proof step + proof *res = m.mk_unit_resolution(pf_args.size(), pf_args.c_ptr(), new_fact); + m_pinned.push_back(res); + return res; + } + + // reduce all units, if any unit reduces to false return true and put its proof into out + bool reduce_units(proof_ref &out) + { + proof_ref res(m); + for (auto entry : m_units) { + reduce(entry.get_value(), res); + if (m.is_false(m.get_fact(res))) { + out = res; + return true; + } + res.reset(); + } + return false; + } + + +public: + reduce_hypotheses(ast_manager &m) : m(m), m_pinned(m) {} + + + void operator()(proof_ref &pr) + { + compute_marks(pr); + if (!reduce_units(pr)) { + reduce(pr.get(), pr); + } + reset(); + } +}; +void reduce_hypotheses(proof_ref &pr) +{ + ast_manager &m = pr.get_manager(); + class reduce_hypotheses hypred(m); + hypred(pr); + DEBUG_CODE(proof_checker pc(m); + expr_ref_vector side(m); + SASSERT(pc.check(pr, side)); + ); +} +} diff --git a/src/muz/spacer/spacer_proof_utils.h b/src/muz/spacer/spacer_proof_utils.h new file mode 100644 index 000000000..7973c673b --- /dev/null +++ b/src/muz/spacer/spacer_proof_utils.h @@ -0,0 +1,43 @@ +/*++ +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.h" + +namespace spacer { +/* + * iterator, which traverses the proof in depth-first post-order. + */ +class ProofIteratorPostOrder { +public: + ProofIteratorPostOrder(proof* refutation, ast_manager& manager); + bool hasNext(); + proof* next(); + +private: + ptr_vector m_todo; + ast_mark m_visited; // the proof nodes we have already visited + + ast_manager& m; +}; + + +void reduce_hypotheses(proof_ref &pr); +} +#endif diff --git a/src/muz/spacer/spacer_prop_solver.cpp b/src/muz/spacer/spacer_prop_solver.cpp new file mode 100644 index 000000000..4f5ad86f2 --- /dev/null +++ b/src/muz/spacer/spacer_prop_solver.cpp @@ -0,0 +1,298 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_prop_solver.cpp + +Abstract: + + SAT solver abstraction for SPACER. + +Author: + + Arie Gurfinkel + Anvesh Komuravelli + +Revision History: + +--*/ + +#include +#include "model.h" +#include "spacer_util.h" +#include "spacer_prop_solver.h" +#include "ast_smt2_pp.h" +#include "dl_util.h" +#include "model_pp.h" +#include "smt_params.h" +#include "datatype_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "spacer_farkas_learner.h" +#include "ast_smt2_pp.h" +#include "expr_replacer.h" +#include "fixedpoint_params.hpp" + +namespace spacer { + +prop_solver::prop_solver(manager& pm, fixedpoint_params const& p, symbol const& name) : + m(pm.get_manager()), + m_pm(pm), + m_name(name), + m_ctx(NULL), + m_pos_level_atoms(m), + m_neg_level_atoms(m), + m_core(0), + m_subset_based_core(false), + m_uses_level(infty_level()), + m_delta_level(false), + m_in_level(false), + m_use_push_bg(p.spacer_keep_proxy()) +{ + + m_solvers[0] = pm.mk_fresh(); + m_fparams[0] = &pm.fparams(); + + 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()); } +} + +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, 0, m.mk_bool_sort()); + 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::assert_level_atoms(unsigned level) +{ + unsigned lev_cnt = level_cnt(); + for (unsigned i = 0; i < lev_cnt; i++) { + bool active = m_delta_level ? i == level : i >= level; + app * lev_atom = + active ? m_neg_level_atoms.get(i) : m_pos_level_atoms.get(i); + m_ctx->push_bg(lev_atom); + } +} + +void prop_solver::assert_expr(expr * form) +{ + SASSERT(!m_in_level); + m_contexts[0]->assert_expr(form); + m_contexts[1]->assert_expr(form); + IF_VERBOSE(21, verbose_stream() << "$ asserted " << mk_pp(form, m) << "\n";); + TRACE("spacer", tout << "add_formula: " << mk_pp(form, m) << "\n";); +} + +void prop_solver::assert_expr(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); + assert_expr(lform); +} + + +/// 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) +{ + // replace expressions by assumption literals + itp_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()); + // if hard constraints alone are unsat or there are no soft + // constraints, we are done + if (res != l_false || soft.empty()) { return res; } + + // clear soft constraints, we will recompute them later + soft.reset(); + + expr_ref saved(m); + ptr_vector core; + m_ctx->get_unsat_core(core); + + // while there are soft constraints + while (hard.size() > hard_sz) { + bool found = false; + // look for a soft constraint that is in the unsat core + for (unsigned i = hard_sz, sz = hard.size(); i < sz; ++i) + if (core.contains(hard.get(i))) { + found = true; + // AG: not sure why we are saving it + saved = hard.get(i); + hard[i] = hard.back(); + hard.pop_back(); + break; + } + // if no soft constraints in the core, return this should + // not happen because it implies that hard alone is unsat + // and that is taken care of earlier + if (!found) { + hard.resize(hard_sz); + return l_false; + } + + // check that the NEW constraints became sat + res = m_ctx->check_sat(hard.size(), hard.c_ptr()); + if (res != l_false) { break; } + // still unsat, update the core and repeat + core.reset(); + m_ctx->get_unsat_core(core); + } + + // update soft with found soft constraints + if (res == l_true) { + for (unsigned i = hard_sz, sz = hard.size(); i < sz; ++i) + { soft.push_back(hard.get(i)); } + } + // revert hard back to the right size + // proxies are undone on exit via scoped_mk_proxy + hard.resize(hard_sz); + return res; +} + +lbool prop_solver::internal_check_assumptions( + expr_ref_vector& hard_atoms, + expr_ref_vector& soft_atoms) +{ + // XXX Turn model generation if m_model != 0 + SASSERT(m_ctx); + SASSERT(m_ctx_fparams); + flet _model(m_ctx_fparams->m_model, m_model != 0); + + if (m_in_level) { assert_level_atoms(m_current_level); } + lbool result = maxsmt(hard_atoms, soft_atoms); + if (result != l_false && m_model) { m_ctx->get_model(*m_model); } + + SASSERT(result != l_false || soft_atoms.empty()); + + /// compute level used in the core + // XXX this is a poor approximation because the core will get minimized further + if (result == l_false) { + ptr_vector core; + m_ctx->get_full_unsat_core(core); + unsigned core_size = core.size(); + m_uses_level = infty_level(); + + for (unsigned i = 0; i < core_size; ++i) { + if (m_level_atoms_set.contains(core[i])) { + unsigned sz = std::min(m_uses_level, m_neg_level_atoms.size()); + for (unsigned j = 0; j < sz; ++j) + if (m_neg_level_atoms [j].get() == core[i]) { + m_uses_level = j; + break; + } + SASSERT(!is_infty_level(m_uses_level)); + } + } + } + + if (result == l_false && m_core && m.proofs_enabled() && !m_subset_based_core) { + TRACE("spacer", tout << "theory core\n";); + m_core->reset(); + m_ctx->get_itp_core(*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 + m_ctx->undo_proxies(*m_core); + } + return result; +} + + + +lbool prop_solver::check_assumptions(const expr_ref_vector & _hard, + expr_ref_vector& soft, + unsigned num_bg, expr * const * bg, + unsigned solver_id) +{ + // current clients expect that flattening of HARD is + // done implicitly during check_assumptions + expr_ref_vector hard(m); + hard.append(_hard.size(), _hard.c_ptr()); + 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); + + for (unsigned i = 0; i < num_bg; ++i) + if (m_use_push_bg) { m_ctx->push_bg(bg [i]); } + else { m_ctx->assert_expr(bg[i]); } + + unsigned soft_sz = soft.size(); +#pragma unused(soft_sz) + lbool res = internal_check_assumptions(hard, soft); + if (!m_use_push_bg) { m_ctx->pop(1); } + + TRACE("psolve_verbose", + tout << "sat: " << mk_pp(mk_and(hard), m) << "\n" + << mk_pp(mk_and(soft), m) << "\n"; + for (unsigned i = 0; i < num_bg; ++i) + tout << "bg" << i << ": " << mk_pp(bg[i], m) << "\n"; + tout << "res: " << res << "\n";); + CTRACE("psolve", m_core, + tout << "core is: " << mk_pp(mk_and(*m_core), m) << "\n";); + + SASSERT(soft_sz >= soft.size()); + + // -- reset all parameters + m_core = 0; + m_model = 0; + m_subset_based_core = false; + return res; +} + +void prop_solver::collect_statistics(statistics& st) const +{ + m_contexts[0]->collect_statistics(st); + m_contexts[1]->collect_statistics(st); +} + +void prop_solver::reset_statistics() +{ +} + + + + +} diff --git a/src/muz/spacer/spacer_prop_solver.h b/src/muz/spacer/spacer_prop_solver.h new file mode 100644 index 000000000..4f5df819b --- /dev/null +++ b/src/muz/spacer/spacer_prop_solver.h @@ -0,0 +1,143 @@ +/** +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_prop_solver.h + +Abstract: + + SAT solver abstraction for SPACER. + +Author: + + Arie Gurfinkel + +Revision History: + +--*/ + +#ifndef _PROP_SOLVER_H_ +#define _PROP_SOLVER_H_ + +#include +#include +#include +#include "ast.h" +#include "obj_hashtable.h" +#include "smt_kernel.h" +#include "util.h" +#include "vector.h" +#include "spacer_manager.h" +#include "spacer_smt_context_manager.h" +#include "spacer_itp_solver.h" + +struct fixedpoint_params; + +namespace spacer { + +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; + 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; + expr_ref_vector* m_core; + model_ref* m_model; + bool m_subset_based_core; + unsigned m_uses_level; + /// if true sets the solver into a delta level, enabling only + /// atoms explicitly asserted in m_current_level + bool m_delta_level; + bool m_in_level; + bool m_use_push_bg; + unsigned m_current_level; // set when m_in_level + + void assert_level_atoms(unsigned level); + + void ensure_level(unsigned lvl); + + lbool internal_check_assumptions(expr_ref_vector &hard, + expr_ref_vector &soft); + + lbool maxsmt(expr_ref_vector &hard, expr_ref_vector &soft); + + +public: + prop_solver(spacer::manager& pm, fixedpoint_params const& p, symbol const& name); + + + 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; } + bool assumes_level() const { return !is_infty_level(m_uses_level); } + unsigned uses_level() const {return m_uses_level;} + + + void add_level(); + unsigned level_cnt() const; + + + void assert_expr(expr * form); + void assert_expr(expr * form, unsigned level); + + /** + * check assumptions with a background formula + */ + lbool check_assumptions(const expr_ref_vector & hard, + expr_ref_vector & soft, + unsigned num_bg = 0, + expr * const *bg = NULL, + unsigned solver_id = 0); + + void collect_statistics(statistics& st) const; + void reset_statistics(); + + 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; } + }; + + class scoped_subset_core { + prop_solver &m_ps; + bool m_subset_based_core; + + public: + scoped_subset_core(prop_solver &ps, bool subset_core) : + m_ps(ps), m_subset_based_core(ps.m_subset_based_core) + {m_ps.set_subset_based_core(subset_core);} + + ~scoped_subset_core() + {m_ps.set_subset_based_core(m_subset_based_core);} + }; + + class scoped_delta_level : public scoped_level { + bool &m_delta; + public: + scoped_delta_level(prop_solver &ps, unsigned lvl) : + scoped_level(ps, lvl), m_delta(ps.m_delta_level) {m_delta = true;} + ~scoped_delta_level() {m_delta = false;} + }; + + +}; +} + + +#endif diff --git a/src/muz/spacer/spacer_qe_project.cpp b/src/muz/spacer/spacer_qe_project.cpp new file mode 100644 index 000000000..cfc5d6449 --- /dev/null +++ b/src/muz/spacer/spacer_qe_project.cpp @@ -0,0 +1,2333 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation and Arie Gurfinkel + +Module Name: + + spacer_qe_project.cpp + +Abstract: + + Simple projection function for real arithmetic based on Loos-W. + Projection functions for arrays based on MBP + +Author: + + Nikolaj Bjorner (nbjorner) 2013-09-12 + Anvesh Komuravelli + Arie Gurfinkel + +Revision History: + + +--*/ + +#include "spacer_qe_project.h" +#include "qe_vartest.h" +#include "qe.h" +#include "arith_decl_plugin.h" +#include "ast_pp.h" +#include "th_rewriter.h" +#include "expr_functors.h" +#include "expr_substitution.h" +#include "expr_replacer.h" +#include "model_pp.h" +#include "expr_safe_replace.h" +#include "model_evaluator.h" +#include "qe_lite.h" +#include "spacer_mev_array.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,...} + */ +class peq { + ast_manager& m; + expr_ref m_lhs; + expr_ref m_rhs; + unsigned m_num_indices; + expr_ref_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; + ast_eq_proc m_eq_proc; // for checking if two asts are equal + +public: + static const char* PARTIAL_EQ; + + peq (app* p, ast_manager& m); + + peq (expr* lhs, expr* rhs, unsigned num_indices, expr * const * diff_indices, ast_manager& m); + + void lhs (expr_ref& result); + + void rhs (expr_ref& result); + + void get_diff_indices (expr_ref_vector& result); + + void mk_peq (app_ref& result); + + void mk_eq (app_ref_vector& aux_consts, app_ref& result, bool stores_on_rhs = true); +}; + +const char* peq::PARTIAL_EQ = "partial_eq"; + +peq::peq (app* p, ast_manager& m): + m (m), + m_lhs (p->get_arg (0), m), + m_rhs (p->get_arg (1), m), + m_num_indices (p->get_num_args ()-2), + m_diff_indices (m), + m_decl (p->get_decl (), m), + m_peq (p, m), + m_eq (m), + m_arr_u (m) +{ + SASSERT (is_partial_eq (p)); + SASSERT (m_arr_u.is_array (m_lhs) && + m_arr_u.is_array (m_rhs) && + m_eq_proc (m.get_sort (m_lhs), m.get_sort (m_rhs))); + for (unsigned i = 2; i < p->get_num_args (); i++) { + m_diff_indices.push_back (p->get_arg (i)); + } +} + +peq::peq (expr* lhs, expr* rhs, unsigned num_indices, expr * const * diff_indices, ast_manager& m): + m (m), + m_lhs (lhs, m), + m_rhs (rhs, m), + m_num_indices (num_indices), + m_diff_indices (m), + 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_eq_proc (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 (unsigned i = 0; i < num_indices; i++) { + sorts.push_back (m.get_sort (diff_indices [i])); + m_diff_indices.push_back (diff_indices [i]); + } + m_decl = m.mk_func_decl (symbol (PARTIAL_EQ), sorts.size (), sorts.c_ptr (), m.mk_bool_sort ()); +} + +void peq::lhs (expr_ref& result) { result = m_lhs; } + +void peq::rhs (expr_ref& result) { result = m_rhs; } + +void peq::get_diff_indices (expr_ref_vector& result) { + for (unsigned i = 0; i < m_diff_indices.size (); i++) { + result.push_back (m_diff_indices.get (i)); + } +} + +void peq::mk_peq (app_ref& result) { + if (!m_peq) { + ptr_vector args; + args.push_back (m_lhs); + args.push_back (m_rhs); + for (unsigned i = 0; i < m_num_indices; i++) { + args.push_back (m_diff_indices.get (i)); + } + m_peq = m.mk_app (m_decl, args.size (), args.c_ptr ()); + } + result = m_peq; +} + +void peq::mk_eq (app_ref_vector& aux_consts, app_ref& result, bool stores_on_rhs) { + 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)); + expr_ref_vector::iterator end = m_diff_indices.end (); + for (expr_ref_vector::iterator it = m_diff_indices.begin (); + it != end; it++) { + app* val = m.mk_fresh_const ("diff", val_sort); + ptr_vector store_args; + store_args.push_back (rhs); + store_args.push_back (*it); + store_args.push_back (val); + rhs = m_arr_u.mk_store (store_args.size (), store_args.c_ptr ()); + aux_consts.push_back (val); + } + m_eq = m.mk_eq (lhs, rhs); + } + result = m_eq; +} + + +bool is_partial_eq (app* a) { + return a->get_decl ()->get_name () == peq::PARTIAL_EQ; +} + +} + + +namespace qe { + + class is_relevant_default : public i_expr_pred { + public: + bool operator()(expr* e) { + return true; + } + }; + + class mk_atom_default : public i_nnf_atom { + public: + virtual void operator()(expr* e, bool pol, expr_ref& result) { + if (pol) result = e; + else result = result.get_manager().mk_not(e); + } + }; + + class arith_project_util { + ast_manager& m; + arith_util a; + th_rewriter m_rw; + expr_ref_vector m_lits; + expr_ref_vector m_terms; + vector m_coeffs; + vector m_divs; + svector m_strict; + svector m_eq; + scoped_ptr m_var; + + bool is_linear(rational const& mul, expr* t, rational& c, expr_ref_vector& ts) { + expr* t1, *t2; + rational mul1; + bool res = true; + if (t == m_var->x()) { + c += mul; + } + else if (a.is_mul(t, t1, t2) && a.is_numeral(t1, mul1)) { + res = is_linear(mul* mul1, t2, c, ts); + } + else if (a.is_mul(t, t1, t2) && a.is_numeral(t2, mul1)) { + res = is_linear(mul* mul1, t1, c, ts); + } + else if (a.is_add(t)) { + app* ap = to_app(t); + for (unsigned i = 0; res && i < ap->get_num_args(); ++i) { + res = is_linear(mul, ap->get_arg(i), c, ts); + } + } + else if (a.is_sub(t, t1, t2)) { + res = is_linear(mul, t1, c, ts) && is_linear(-mul, t2, c, ts); + } + else if (a.is_uminus(t, t1)) { + res = is_linear(-mul, t1, c, ts); + } + else if (a.is_numeral(t, mul1)) { + ts.push_back(a.mk_numeral(mul*mul1, m.get_sort(t))); + } + else if ((*m_var)(t)) { + IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(t, m) << "\n";); + TRACE ("qe", tout << "Failed to project: " << mk_pp (t, m) << "\n";); + res = false; + } + else if (mul.is_one()) { + ts.push_back(t); + } + else { + ts.push_back(a.mk_mul(a.mk_numeral(mul, m.get_sort(t)), t)); + } + return res; + } + + // either an equality (cx + t = 0) or an inequality (cx + t <= 0) or a divisibility literal (d | cx + t) + bool is_linear(expr* lit, rational& c, expr_ref& t, rational& d, bool& is_strict, bool& is_eq, bool& is_diseq) { + SASSERT ((*m_var)(lit)); + expr* e1, *e2; + c.reset(); + sort* s; + expr_ref_vector ts(m); + bool is_not = m.is_not(lit, lit); + rational mul(1); + if (is_not) { + mul.neg(); + } + SASSERT(!m.is_not(lit)); + if (a.is_le(lit, e1, e2) || a.is_ge(lit, e2, e1)) { + if (!is_linear( mul, e1, c, ts) || !is_linear(-mul, e2, c, ts)) + return false; + s = m.get_sort(e1); + is_strict = is_not; + } + else if (a.is_lt(lit, e1, e2) || a.is_gt(lit, e2, e1)) { + if (!is_linear( mul, e1, c, ts) || !is_linear(-mul, e2, c, ts)) + return false; + s = m.get_sort(e1); + is_strict = !is_not; + } + else if (m.is_eq(lit, e1, e2) && a.is_int_real (e1)) { + expr *t, *num; + rational num_val, d_val, z; + bool is_int; + if (a.is_mod (e1, t, num) && a.is_numeral (num, num_val, is_int) && is_int && + a.is_numeral (e2, z) && z.is_zero ()) { + // divsibility constraint: t % num == 0 <=> num | t + if (num_val.is_zero ()) { + IF_VERBOSE(1, verbose_stream() << "div by zero" << mk_pp(lit, m) << "\n";); + return false; + } + d = num_val; + if (!is_linear (mul, t, c, ts)) return false; + } else if (a.is_mod (e2, t, num) && a.is_numeral (num, num_val, is_int) && is_int && + a.is_numeral (e1, z) && z.is_zero ()) { + // divsibility constraint: 0 == t % num <=> num | t + if (num_val.is_zero ()) { + IF_VERBOSE(1, verbose_stream() << "div by zero" << mk_pp(lit, m) << "\n";); + return false; + } + d = num_val; + if (!is_linear (mul, t, c, ts)) return false; + } else { + // equality or disequality + if (!is_linear( mul, e1, c, ts) || !is_linear(-mul, e2, c, ts)) + return false; + if (is_not) is_diseq = true; + else is_eq = true; + } + s = m.get_sort(e1); + } + else { + IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(lit, m) << "\n";); + TRACE ("qe", tout << "Failed to project: " << mk_pp (lit, m) << "\n";); + return false; + } + + if (ts.empty()) { + t = a.mk_numeral(rational(0), s); + } + else if (ts.size () == 1) { + t = ts.get (0); + } + else { + t = a.mk_add(ts.size(), ts.c_ptr()); + } + + return true; + } + + bool project(model& mdl, expr_ref_vector& lits) { + unsigned num_pos = 0; + unsigned num_neg = 0; + bool use_eq = false; + expr_ref_vector new_lits(m); + expr_ref eq_term (m); + + m_lits.reset (); + m_terms.reset(); + m_coeffs.reset(); + m_strict.reset(); + m_eq.reset (); + + for (unsigned i = 0; i < lits.size(); ++i) { + rational c(0), d(0); + expr_ref t(m); + bool is_strict = false; + bool is_eq = false; + bool is_diseq = false; + if (!(*m_var)(lits.get (i))) { + new_lits.push_back(lits.get (i)); + continue; + } + if (is_linear(lits.get (i), c, t, d, is_strict, is_eq, is_diseq)) { + if (c.is_zero()) { + m_rw(lits.get (i), t); + new_lits.push_back(t); + } else if (is_eq) { + if (!use_eq) { + // c*x + t = 0 <=> x = -t/c + eq_term = mk_mul (-(rational::one ()/c), t); + use_eq = true; + } + m_lits.push_back (lits.get (i)); + m_coeffs.push_back(c); + m_terms.push_back(t); + m_strict.push_back(false); + m_eq.push_back (true); + } else { + if (is_diseq) { + // c*x + t != 0 + // find out whether c*x + t < 0, or c*x + t > 0 + expr_ref cx (m), cxt (m), val (m); + rational r; + cx = mk_mul (c, m_var->x()); + cxt = mk_add (cx, t); + VERIFY(mdl.eval(cxt, val, true)); + VERIFY(a.is_numeral(val, r)); + SASSERT (r > rational::zero () || r < rational::zero ()); + if (r > rational::zero ()) { + c = -c; + t = mk_mul (-(rational::one()), t); + } + is_strict = true; + } + m_lits.push_back (lits.get (i)); + m_coeffs.push_back(c); + m_terms.push_back(t); + m_strict.push_back(is_strict); + m_eq.push_back (false); + if (c.is_pos()) { + ++num_pos; + } + else { + ++num_neg; + } + } + } + else return false; + } + if (use_eq) { + TRACE ("qe", + tout << "Using equality term: " << mk_pp (eq_term, m) << "\n"; + ); + // substitute eq_term for x everywhere + for (unsigned i = 0; i < m_lits.size(); ++i) { + expr_ref cx (m), cxt (m), z (m), result (m); + cx = mk_mul (m_coeffs[i], eq_term); + cxt = mk_add (cx, m_terms.get(i)); + z = a.mk_numeral(rational(0), m.get_sort(eq_term)); + if (m_eq[i]) { + // c*x + t = 0 + result = a.mk_eq (cxt, z); + } else if (m_strict[i]) { + // c*x + t < 0 + result = a.mk_lt (cxt, z); + } else { + // c*x + t <= 0 + result = a.mk_le (cxt, z); + } + m_rw (result); + new_lits.push_back (result); + } + } + lits.reset(); + lits.append(new_lits); + if (use_eq || num_pos == 0 || num_neg == 0) { + return true; + } + bool use_pos = num_pos < num_neg; + unsigned max_t = find_max(mdl, use_pos); + + expr_ref new_lit (m); + for (unsigned i = 0; i < m_lits.size(); ++i) { + if (i != max_t) { + if (m_coeffs[i].is_pos() == use_pos) { + new_lit = mk_le(i, max_t); + } + else { + new_lit = mk_lt(i, max_t); + } + lits.push_back(new_lit); + TRACE ("qe", + tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; + tout << "New literal: " << mk_pp (new_lit, m) << "\n"; + ); + } + } + return true; + } + + bool project(model& mdl, app_ref_vector const& lits, expr_map& map, app_ref& div_lit) { + unsigned num_pos = 0; // number of positive literals true in the model + unsigned num_neg = 0; // number of negative literals true in the model + + m_lits.reset (); + m_terms.reset(); + m_coeffs.reset(); + m_divs.reset (); + m_strict.reset(); + m_eq.reset (); + + expr_ref var_val (m); + VERIFY (mdl.eval (m_var->x(), var_val, true)); + + unsigned eq_idx = lits.size (); + for (unsigned i = 0; i < lits.size(); ++i) { + rational c(0), d(0); + expr_ref t(m); + bool is_strict = false; + bool is_eq = false; + bool is_diseq = false; + if (!(*m_var)(lits.get (i))) continue; + if (is_linear(lits.get (i), c, t, d, is_strict, is_eq, is_diseq)) { + TRACE ("qe", + tout << "Literal: " << mk_pp (lits.get (i), m) << "\n"; + ); + + if (c.is_zero()) { + TRACE ("qe", + tout << "independent of variable\n"; + ); + continue; + } + + // evaluate c*x + t in the model + expr_ref cx (m), cxt (m), val (m); + rational r; + cx = mk_mul (c, m_var->x()); + cxt = mk_add (cx, t); + VERIFY(mdl.eval(cxt, val, true)); + VERIFY(a.is_numeral(val, r)); + + if (is_eq) { + TRACE ("qe", + tout << "equality term\n"; + ); + // check if the equality is true in the mdl + if (eq_idx == lits.size () && r == rational::zero ()) { + eq_idx = m_lits.size (); + } + m_lits.push_back (lits.get (i)); + m_coeffs.push_back(c); + m_terms.push_back(t); + m_strict.push_back(false); + m_eq.push_back (true); + m_divs.push_back (d); + } else { + TRACE ("qe", + tout << "not an equality term\n"; + ); + if (is_diseq) { + // c*x + t != 0 + // find out whether c*x + t < 0, or c*x + t > 0 + if (r > rational::zero ()) { + c = -c; + t = mk_mul (-(rational::one()), t); + r = -r; + } + // note: if the disequality is false in the model, + // r==0 and we end up choosing c*x + t < 0 + is_strict = true; + } + m_lits.push_back (lits.get (i)); + m_coeffs.push_back(c); + m_terms.push_back(t); + m_strict.push_back(is_strict); + m_eq.push_back (false); + m_divs.push_back (d); + if (d.is_zero ()) { // not a div term + if ((is_strict && r < rational::zero ()) || + (!is_strict && r <= rational::zero ())) { // literal true in the model + if (c.is_pos()) { + ++num_pos; + } + else { + ++num_neg; + } + } + } + } + TRACE ("qe", + tout << "c: " << c << "\n"; + tout << "t: " << mk_pp (t, m) << "\n"; + tout << "d: " << d << "\n"; + ); + } + else return false; + } + + rational lcm_coeffs (1), lcm_divs (1); + if (a.is_int (m_var->x())) { + // lcm of (absolute values of) coeffs + for (unsigned i = 0; i < m_lits.size (); i++) { + lcm_coeffs = lcm (lcm_coeffs, abs (m_coeffs[i])); + } + // normalize coeffs of x to +/-lcm_coeffs and scale terms and divs appropriately; + // find lcm of scaled-up divs + for (unsigned i = 0; i < m_lits.size (); i++) { + rational factor (lcm_coeffs / abs(m_coeffs[i])); + if (!factor.is_one () && !a.is_zero (m_terms.get (i))) + m_terms[i] = a.mk_mul (a.mk_numeral (factor, a.mk_int ()), + m_terms.get (i)); + m_coeffs[i] = (m_coeffs[i].is_pos () ? lcm_coeffs : -lcm_coeffs); + if (!m_divs[i].is_zero ()) { + m_divs[i] *= factor; + lcm_divs = lcm (lcm_divs, m_divs[i]); + } + TRACE ("qe", + tout << "normalized coeff: " << m_coeffs[i] << "\n"; + tout << "normalized term: " << mk_pp (m_terms.get (i), m) << "\n"; + tout << "normalized div: " << m_divs[i] << "\n"; + ); + } + + // consider new divisibility literal (lcm_coeffs | (lcm_coeffs * x)) + lcm_divs = lcm (lcm_divs, lcm_coeffs); + + TRACE ("qe", + tout << "lcm of coeffs: " << lcm_coeffs << "\n"; + tout << "lcm of divs: " << lcm_divs << "\n"; + ); + } + + expr_ref z (a.mk_numeral (rational::zero (), a.mk_int ()), m); + expr_ref x_term_val (m); + + // use equality term + if (eq_idx < lits.size ()) { + if (a.is_real (m_var->x ())) { + // c*x + t = 0 <=> x = -t/c + expr_ref eq_term (mk_mul (-(rational::one ()/m_coeffs[eq_idx]), m_terms.get (eq_idx)), m); + m_rw (eq_term); + map.insert (m_var->x (), eq_term, 0); + TRACE ("qe", + tout << "Using equality term: " << mk_pp (eq_term, m) << "\n"; + ); + } + else { + // find substitution term for (lcm_coeffs * x) + if (m_coeffs[eq_idx].is_pos ()) { + x_term_val = a.mk_uminus (m_terms.get (eq_idx)); + } else { + x_term_val = m_terms.get (eq_idx); + } + m_rw (x_term_val); + TRACE ("qe", + tout << "Using equality literal: " << mk_pp (m_lits.get (eq_idx), m) << "\n"; + tout << "substitution for (lcm_coeffs * x): " << mk_pp (x_term_val, m) << "\n"; + ); + // can't simply substitute for x; need to explicitly substitute the lits + mk_lit_substitutes (x_term_val, map, eq_idx); + + if (!lcm_coeffs.is_one ()) { + // new div constraint: lcm_coeffs | x_term_val + div_lit = m.mk_eq (a.mk_mod (x_term_val, + a.mk_numeral (lcm_coeffs, a.mk_int ())), + z); + } + } + + return true; + } + + expr_ref new_lit (m); + + if (num_pos == 0 || num_neg == 0) { + TRACE ("qe", + if (num_pos == 0) { + tout << "virtual substitution with +infinity\n"; + } else { + tout << "virtual substitution with -infinity\n"; + } + ); + + /** + * make all equalities false; + * if num_pos = 0 (num_neg = 0), make all positive (negative) inequalities false; + * make the rest inequalities true; + * substitute value of x under given model for the rest (div terms) + */ + + if (a.is_int (m_var->x())) { + // to substitute for (lcm_coeffs * x), it suffices to pick + // some element in the congruence class of (lcm_coeffs * x) mod lcm_divs; + // simply substituting var_val for x in the literals does this job; + // but to keep constants small, we use (lcm_coeffs * var_val) % lcm_divs instead + rational var_val_num; + VERIFY (a.is_numeral (var_val, var_val_num)); + x_term_val = a.mk_numeral (mod (lcm_coeffs * var_val_num, lcm_divs), + a.mk_int ()); + TRACE ("qe", + tout << "Substitution for (lcm_coeffs * x): " + << mk_pp (x_term_val, m) << "\n"; + ); + } + for (unsigned i = 0; i < m_lits.size (); i++) { + if (!m_divs[i].is_zero ()) { + // m_divs[i] | (x_term_val + m_terms[i]) + + // -- x_term_val is the absolute value, negate it if needed + if (m_coeffs.get (i).is_pos ()) + new_lit = a.mk_add (m_terms.get (i), x_term_val); + else + new_lit = a.mk_add (m_terms.get (i), a.mk_uminus (x_term_val)); + + // XXX Our handling of divisibility constraints is very fragile. + // XXX Rewrite before applying divisibility to preserve syntactic structure + m_rw(new_lit); + new_lit = m.mk_eq (a.mk_mod (new_lit, + a.mk_numeral (m_divs[i], a.mk_int ())), z); + } else if (m_eq[i] || + (num_pos == 0 && m_coeffs[i].is_pos ()) || + (num_neg == 0 && m_coeffs[i].is_neg ())) { + new_lit = m.mk_false (); + } else { + new_lit = m.mk_true (); + } + map.insert (m_lits.get (i), new_lit, 0); + TRACE ("qe", + tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; + tout << "New literal: " << mk_pp (new_lit, m) << "\n"; + ); + } + return true; + } + + bool use_pos = num_pos < num_neg; // pick a side; both are sound + + unsigned max_t = find_max(mdl, use_pos); + + TRACE ("qe", + if (use_pos) { + tout << "virtual substitution with upper bound:\n"; + } else { + tout << "virtual substitution with lower bound:\n"; + } + tout << "test point: " << mk_pp (m_lits.get (max_t), m) << "\n"; + tout << "coeff: " << m_coeffs[max_t] << "\n"; + tout << "term: " << mk_pp (m_terms.get (max_t), m) << "\n"; + tout << "is_strict: " << m_strict[max_t] << "\n"; + ); + + if (a.is_real (m_var->x ())) { + for (unsigned i = 0; i < m_lits.size(); ++i) { + if (i != max_t) { + if (m_eq[i]) { + if (!m_strict[max_t]) { + new_lit = mk_eq (i, max_t); + } else { + new_lit = m.mk_false (); + } + } else if (m_coeffs[i].is_pos() == use_pos) { + new_lit = mk_le (i, max_t); + } else { + new_lit = mk_lt (i, max_t); + } + } else { + new_lit = m.mk_true (); + } + map.insert (m_lits.get (i), new_lit, 0); + TRACE ("qe", + tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; + tout << "New literal: " << mk_pp (new_lit, m) << "\n"; + ); + } + } else { + SASSERT (a.is_int (m_var->x ())); + + // mk substitution term for (lcm_coeffs * x) + + // evaluate c*x + t for the literal at max_t + expr_ref cx (m), cxt (m), val (m); + 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)); + VERIFY(a.is_numeral(val, r)); + + // get the offset from the smallest/largest possible value for x + // literal smallest/largest val of x + // ------- -------------------------- + // l < x l+1 + // l <= x l + // x < u u-1 + // x <= u u + rational offset; + if (m_strict[max_t]) { + offset = abs(r) - rational::one (); + } else { + offset = abs(r); + } + // obtain the offset modulo lcm_divs + offset %= lcm_divs; + + // for strict negative literal (i.e. strict lower bound), + // substitution term is (t+1+offset); for non-strict, it's (t+offset) + // + // for positive term, subtract from 0 + x_term_val = mk_add (m_terms.get (max_t), a.mk_numeral (offset, a.mk_int ())); + if (m_strict[max_t]) { + x_term_val = a.mk_add (x_term_val, a.mk_numeral (rational::one(), a.mk_int ())); + } + if (m_coeffs[max_t].is_pos ()) { + x_term_val = a.mk_uminus (x_term_val); + } + m_rw (x_term_val); + + TRACE ("qe", + tout << "substitution for (lcm_coeffs * x): " << mk_pp (x_term_val, m) << "\n"; + ); + + // obtain substitutions for all literals in map + mk_lit_substitutes (x_term_val, map, max_t); + + if (!lcm_coeffs.is_one ()) { + // new div constraint: lcm_coeffs | x_term_val + div_lit = m.mk_eq (a.mk_mod (x_term_val, + a.mk_numeral (lcm_coeffs, a.mk_int ())), + z); + } + } + return true; + } + + unsigned find_max(model& mdl, bool do_pos) { + unsigned result; + bool found = false; + bool found_strict = false; + rational found_val (0), r, r_plus_x, found_c; + expr_ref val(m); + + // evaluate x in mdl + rational r_x; + VERIFY(mdl.eval(m_var->x (), val, true)); + 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)); + VERIFY(a.is_numeral(val, r)); + r /= abs(ac); + // skip the literal if false in the model + if (do_pos) { r_plus_x = r + r_x; } + else { r_plus_x = r - r_x; } + if (!((m_strict[i] && r_plus_x < rational::zero ()) || + (!m_strict[i] && r_plus_x <= rational::zero ()))) { + continue; + } + IF_VERBOSE(2, verbose_stream() << "max: " << mk_pp(m_terms.get (i), m) << " " << r << " " << + (!found || r > found_val || (r == found_val && !found_strict && m_strict[i])) << "\n";); + if (!found || r > found_val || (r == found_val && !found_strict && m_strict[i])) { + result = i; + found_val = r; + found_c = ac; + found = true; + found_strict = m_strict[i]; + } + } + } + SASSERT(found); + return result; + } + + // ax + t <= 0 + // bx + s <= 0 + // a and b have different signs. + // Infer: a|b|x + |b|t + |a|bx + |a|s <= 0 + // e.g. |b|t + |a|s <= 0 + expr_ref mk_lt(unsigned i, unsigned j) { + rational const& ac = m_coeffs[i]; + rational const& bc = m_coeffs[j]; + SASSERT(ac.is_pos() != bc.is_pos()); + SASSERT(ac.is_neg() != bc.is_neg()); + expr_ref bt (m), as (m), ts (m), z (m); + expr* t = m_terms.get (i); + expr* s = m_terms.get (j); + bt = mk_mul(abs(bc), t); + as = mk_mul(abs(ac), s); + ts = mk_add(bt, as); + z = a.mk_numeral(rational(0), m.get_sort(t)); + expr_ref result1(m), result2(m); + if (m_strict[i] || m_strict[j]) { + result1 = a.mk_lt(ts, z); + } + else { + result1 = a.mk_le(ts, z); + } + m_rw(result1, result2); + return result2; + } + + // ax + t <= 0 + // bx + s <= 0 + // a and b have same signs. + // encode:// t/|a| <= s/|b| + // e.g. |b|t <= |a|s + expr_ref mk_le(unsigned i, unsigned j) { + rational const& ac = m_coeffs[i]; + rational const& bc = m_coeffs[j]; + SASSERT(ac.is_pos() == bc.is_pos()); + SASSERT(ac.is_neg() == bc.is_neg()); + expr_ref bt (m), as (m); + expr* t = m_terms.get (i); + expr* s = m_terms.get (j); + bt = mk_mul(abs(bc), t); + as = mk_mul(abs(ac), s); + expr_ref result1(m), result2(m); + if (!m_strict[j] && m_strict[i]) { + result1 = a.mk_lt(bt, as); + } + else { + result1 = a.mk_le(bt, as); + } + m_rw(result1, result2); + return result2; + } + + // ax + t = 0 + // bx + s <= 0 + // replace equality by (-t/a == -s/b), or, as = bt + expr_ref mk_eq (unsigned i, unsigned j) { + expr_ref as (m), bt (m); + as = mk_mul (m_coeffs[i], m_terms.get (j)); + bt = mk_mul (m_coeffs[j], m_terms.get (i)); + expr_ref result (m); + result = m.mk_eq (as, bt); + m_rw (result); + return result; + } + + + expr* mk_add(expr* t1, expr* t2) { + return a.mk_add(t1, t2); + } + expr* mk_mul(rational const& r, expr* t2) { + expr* t1 = a.mk_numeral(r, m.get_sort(t2)); + return a.mk_mul(t1, t2); + } + + /** + * walk the ast of fml and introduce a fresh variable for every mod term + * (updating the mdl accordingly) + */ + void factor_mod_terms (expr_ref& fml, app_ref_vector& vars, model& mdl) { + expr_ref_vector todo (m), eqs (m); + expr_map factored_terms (m); + ast_mark done; + + todo.push_back (fml); + while (!todo.empty ()) { + expr* e = todo.back (); + if (!is_app (e) || done.is_marked (e)) { + todo.pop_back (); + continue; + } + app* ap = to_app (e); + unsigned num_args = ap->get_num_args (); + bool all_done = true, changed = false; + expr_ref_vector args (m); + for (unsigned i = 0; i < num_args; i++) { + expr* old_arg = ap->get_arg (i); + if (!done.is_marked (old_arg)) { + todo.push_back (old_arg); + all_done = false; + } + if (!all_done) continue; + // all args so far have been processed + // get the correct arg to use + proof* pr = 0; expr* new_arg = 0; + factored_terms.get (old_arg, new_arg, pr); + if (new_arg) { + // changed + args.push_back (new_arg); + changed = true; + } + else { + // not changed + args.push_back (old_arg); + } + } + if (all_done) { + // all args processed; make new term + func_decl* d = ap->get_decl (); + expr_ref new_term (m); + new_term = m.mk_app (d, args.size (), args.c_ptr ()); + // check for mod and introduce new var + if (a.is_mod (ap)) { + app_ref new_var (m); + 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); + // use the variable from now on + new_term = new_var; + changed = true; + // update vars and mdl + vars.push_back (new_var); + mdl.register_decl (new_var->get_decl (), val); + } + if (changed) { + factored_terms.insert (e, new_term, 0); + } + done.mark (e, true); + todo.pop_back (); + } + } + + // mk new fml + proof* pr = 0; expr* new_fml = 0; + factored_terms.get (fml, new_fml, pr); + if (new_fml) { + fml = new_fml; + // add in eqs + fml = m.mk_and (fml, m.mk_and (eqs.size (), eqs.c_ptr ())); + } + else { + // unchanged + SASSERT (eqs.empty ()); + } + } + + /** + * factor out mod terms by using divisibility terms; + * + * for now, only handle mod equalities of the form (t1 % num == t2), + * replacing it by the equivalent (num | (t1-t2)) /\ (0 <= t2 < abs(num)); + * the divisibility atom is a special mod term ((t1-t2) % num == 0) + */ + void mod2div (expr_ref& fml, expr_map& map) { + expr* new_fml = 0; + + proof *pr = 0; + map.get (fml, new_fml, pr); + if (new_fml) { + fml = new_fml; + return; + } + + expr_ref z (a.mk_numeral (rational::zero (), a.mk_int ()), m); + bool is_mod_eq = false; + + expr *e1, *e2, *num; + expr_ref t1 (m), t2 (m); + rational num_val; + bool is_int; + // check if fml is a mod equality (t1 % num) == t2 + if (m.is_eq (fml, e1, e2)) { + expr* t; + if (a.is_mod (e1, t, num) && a.is_numeral (num, num_val, is_int) && is_int) { + t1 = t; + t2 = e2; + is_mod_eq = true; + } else if (a.is_mod (e2, t, num) && a.is_numeral (num, num_val, is_int) && is_int) { + t1 = t; + t2 = e1; + is_mod_eq = true; + } + } + + if (is_mod_eq) { + // recursively mod2div for t1 and t2 + mod2div (t1, map); + mod2div (t2, map); + + rational t2_num; + if (a.is_numeral (t2, t2_num) && t2_num.is_zero ()) { + // already in the desired form; + // new_fml is (num_val | t1) + new_fml = m.mk_eq (a.mk_mod (t1, a.mk_numeral (num_val, a.mk_int ())), + z); + } + else { + expr_ref_vector lits (m); + // num_val | (t1 - t2) + lits.push_back (m.mk_eq (a.mk_mod (a.mk_sub (t1, t2), + a.mk_numeral (num_val, a.mk_int ())), + z)); + // 0 <= t2 + lits.push_back (a.mk_le (z, t2)); + // t2 < abs (num_val) + lits.push_back (a.mk_lt (t2, a.mk_numeral (abs (num_val), a.mk_int ()))); + + new_fml = m.mk_and (lits.size (), lits.c_ptr ()); + } + } + else if (!is_app (fml)) { + new_fml = fml; + } + else { + app* a = to_app (fml); + expr_ref_vector children (m); + expr_ref ch (m); + for (unsigned i = 0; i < a->get_num_args (); i++) { + ch = a->get_arg (i); + mod2div (ch, map); + children.push_back (ch); + } + new_fml = m.mk_app (a->get_decl (), children.size (), children.c_ptr ()); + } + + map.insert (fml, new_fml, 0); + fml = new_fml; + } + + void collect_lits (expr* fml, app_ref_vector& lits) { + expr_ref_vector todo (m); + ast_mark visited; + todo.push_back(fml); + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (visited.is_marked(e)) { + continue; + } + visited.mark(e, true); + if (!is_app(e)) { + continue; + } + app* a = to_app(e); + if (m.is_and(a) || m.is_or(a)) { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + todo.push_back(a->get_arg(i)); + } + } else { + lits.push_back (a); + } + } + SASSERT(todo.empty()); + visited.reset(); + } + + /** + * assume that all coeffs of x are the same, say c + * substitute x_term_val for (c*x) in all lits and update map + * make the literal at idx true + */ + void mk_lit_substitutes (expr_ref const& x_term_val, expr_map& map, unsigned idx) { + expr_ref z (a.mk_numeral (rational::zero (), a.mk_int ()), m); + expr_ref cxt (m), new_lit (m); + for (unsigned i = 0; i < m_lits.size(); ++i) { + if (i == idx) { + new_lit = m.mk_true (); + } else { + // cxt + if (m_coeffs[i].is_neg ()) { + cxt = a.mk_sub (m_terms.get (i), x_term_val); + } else { + cxt = a.mk_add (m_terms.get (i), x_term_val); + } + + if (m_divs[i].is_zero ()) { + if (m_eq[i]) { + new_lit = m.mk_eq (cxt, z); + } else if (m_strict[i]) { + new_lit = a.mk_lt (cxt, z); + } else { + new_lit = a.mk_le (cxt, z); + } + m_rw(new_lit); + } else { + // div term + // XXX rewrite before applying mod to ensure mod is the top-level operator + m_rw(cxt); + new_lit = m.mk_eq (a.mk_mod (cxt, + a.mk_numeral (m_divs[i], a.mk_int ())), + z); + } + } + map.insert (m_lits.get (i), new_lit, 0); + TRACE ("qe", + tout << "Old literal: " << mk_pp (m_lits.get (i), m) << "\n"; + tout << "New literal: " << mk_pp (new_lit, m) << "\n"; + ); + } + } + + void substitute (expr_ref& fml, app_ref_vector& lits, expr_map& map) { + expr_substitution sub (m); + // literals + for (unsigned i = 0; i < lits.size (); i++) { + expr* new_lit = 0; proof* pr = 0; + app* old_lit = lits.get (i); + map.get (old_lit, new_lit, pr); + if (new_lit) { + sub.insert (old_lit, new_lit); + TRACE ("qe", + tout << "old lit " << mk_pp (old_lit, m) << "\n"; + tout << "new lit " << mk_pp (new_lit, m) << "\n"; + ); + } + } + // substitute for x, if any + expr* x_term = 0; proof* pr = 0; + map.get (m_var->x (), x_term, pr); + if (x_term) { + sub.insert (m_var->x (), x_term); + TRACE ("qe", + tout << "substituting " << mk_pp (m_var->x (), m) << " by " << mk_pp (x_term, m) << "\n"; + ); + } + scoped_ptr rep = mk_default_expr_replacer (m); + rep->set_substitution (&sub); + (*rep)(fml); + } + + public: + arith_project_util(ast_manager& m): + m(m), a(m), m_rw(m), m_lits (m), m_terms (m) {} + + // OLD AND UNUSED INTERFACE + expr_ref operator()(model& mdl, app_ref_vector& vars, expr_ref_vector const& lits) { + app_ref_vector new_vars(m); + expr_ref_vector result(lits); + for (unsigned i = 0; i < vars.size(); ++i) { + app* v = vars.get (i); + m_var = alloc(contains_app, m, v); + bool fail = a.is_int (v) || !project (mdl, result); + if (fail) new_vars.push_back (v); + + IF_VERBOSE(2, + if (fail) { + verbose_stream() << "can't project:" << mk_pp(v, m) << "\n"; + } + ); + TRACE("qe", + if (!fail) { + tout << "projected: " << mk_pp(v, m) << "\n"; + for (unsigned i = 0; i < result.size(); ++i) { + tout << mk_pp(result.get (i), m) << "\n"; + } + } + else { + tout << "Failed to project: " << mk_pp (v, m) << "\n"; + } + ); + } + vars.reset(); + vars.append(new_vars); + return mk_and(result); + } + + void operator()(model& mdl, app_ref_vector& vars, expr_ref& fml) { + expr_map map (m); + operator()(mdl, vars, fml, map); + } + + void operator()(model& mdl, app_ref_vector& vars, expr_ref& fml, expr_map& map) { + app_ref_vector new_vars(m); + + // factor out mod terms by introducing new variables + TRACE ("qe", + tout << "before factoring out mod terms:" << "\n"; + tout << mk_pp (fml, m) << "\n"; + tout << "mdl:\n"; + model_pp (tout, mdl); + tout << "\n"; + ); + + factor_mod_terms (fml, vars, mdl); + + TRACE ("qe", + tout << "after factoring out mod terms:" << "\n"; + tout << mk_pp (fml, m) << "\n"; + tout << "updated mdl:\n"; + model_pp (tout, mdl); + tout << "\n"; + ); + + app_ref_vector lits (m); +// expr_map map (m); + for (unsigned i = 0; i < vars.size(); ++i) { + app* v = vars.get (i); + TRACE ("qe", + tout << "projecting variable: " << mk_pp (v, m) << "\n"; + ); + m_var = alloc(contains_app, m, v); + map.reset (); + lits.reset (); + if (a.is_int (v)) { + // factor out mod terms using div terms + expr_map mod_map (m); + mod2div (fml, mod_map); + TRACE ("qe", + tout << "after mod2div:" << "\n"; + tout << mk_pp (fml, m) << "\n"; + ); + } + collect_lits (fml, lits); + app_ref div_lit (m); + if (project (mdl, lits, map, div_lit)) { + substitute (fml, lits, map); + if (div_lit) { + fml = m.mk_and (fml, div_lit); + } + TRACE("qe", + tout << "projected: " << mk_pp(v, m) << " " + << mk_pp(fml, m) << "\n"; + ); + } + else { + IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(v, m) << "\n";); + TRACE ("qe", tout << "Failed to project: " << mk_pp (v, m) << "\n";); + new_vars.push_back(v); + } + } + vars.reset(); + vars.append(new_vars); + m_rw (fml); + } + }; + + + class array_project_eqs_util { + ast_manager& m; + array_util m_arr_u; + model_ref M; + 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; + model_evaluator_array_util m_mev; + + void reset_v () { + m_v = 0; + m_has_stores_v.reset (); + m_subst_term_v = 0; + m_true_sub_v.reset (); + m_false_sub_v.reset (); + m_aux_lits_v.reset (); + m_idx_lits_v.reset (); + } + + void reset () { + M = 0; + 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, expr_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; + unsigned num_args = a->get_num_args (); + for (unsigned i = 0; i < num_args; i++) { + expr* arg = a->get_arg (i); + 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 + if (m.is_eq (a)) { + expr* a0 = to_app (a)->get_arg (0); + expr* a1 = to_app (a)->get_arg (1); + 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 (unsigned i = 0; i < a->get_num_args (); i++) { + expr* arg = a->get_arg (i); + if (!is_app (arg)) continue; + 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 = 0; proof* pr; + sel_cache.get (arg, arg_new, pr); + if (!arg_new) { + arg_new = arg; + } + 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); + m_mev.eval (*M, a_new, val); + 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, 0); + pinned.push_back (a_new); + } + done.mark (a, true); + } + expr* res = 0; 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); + p.mk_eq (diff_val_consts, eq, stores_on_rhs); + m_aux_vars.append (diff_val_consts); + // extend M to include diff_val_consts + expr_ref arr (m); + expr_ref_vector I (m); + p.lhs (arr); + 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.push_back (I.get (i)); + 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 + m_mev.eval (*M, val_term, val); + 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 + */ + void mk_peq (expr* e0, expr* e1, unsigned num_indices, expr* const* indices, app_ref& result) { + peq p (e0, e1, num_indices, indices, m); + p.mk_peq (result); + } + + void find_subst_term (app* eq) { + app_ref p_exp (m); + mk_peq (eq->get_arg (0), eq->get_arg (1), 0, 0, p_exp); + bool subst_eq_found = false; + while (true) { + TRACE ("qe", + tout << "processing peq:\n"; + tout << mk_pp (p_exp, m) << "\n"; + ); + + peq p (p_exp, m); + expr_ref lhs (m), rhs (m); + p.lhs (lhs); p.rhs (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))) + */ + expr_ref_vector I (m); + p.get_diff_indices (I); + app* a_lhs = to_app (lhs); + expr* arr0 = a_lhs->get_arg (0); + expr* idx = a_lhs->get_arg (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 val (m); + m_mev.eval (*M, idx, val); + for (unsigned i = 0; i < I.size () && !idx_in_I; i++) { + if (idx == I.get (i)) { + idx_in_I = true; + } + else { + expr_ref val1 (m); + expr* idx1 = I.get (i); + expr_ref idx_eq (m.mk_eq (idx, idx1), m); + m_mev.eval (*M, idx1, val1); + if (val == val1) { + idx_in_I = true; + m_idx_lits_v.push_back (idx_eq); + } + else { + idx_diseq.push_back (m.mk_not (idx_eq)); + } + } + } + } + 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 + mk_peq (arr0, arr1, I.size (), I.c_ptr (), p_exp); + + 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 (idx); + mk_peq (arr0, arr1, I.size (), I.c_ptr (), p_exp); + + TRACE ("qe", + tout << "new peq:\n"; + tout << mk_pp (p_exp, m) << "\n"; + ); + + // arr1[idx] == x + ptr_vector sel_args; + sel_args.push_back (arr1); + sel_args.push_back (idx); + 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 (); + } + } + + // 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"; + ); + } + } + + /** + * try to substitute for m_v, using array equalities + * + * compute substitution term and aux lits + */ + bool project (expr_ref const& fml) { + expr_ref_vector eqs (m); + ptr_vector true_eqs; // subset of eqs; eqs ensures references + + find_arr_eqs (fml, eqs); + TRACE ("qe", + tout << "array equalities:\n"; + for (unsigned i = 0; i < eqs.size (); i++) { + tout << mk_pp (eqs.get (i), m) << "\n"; + } + ); + + // evaluate eqs in M + for (unsigned i = 0; i < eqs.size (); i++) { + TRACE ("qe", + tout << "array equality:\n"; + tout << mk_pp (eqs.get (i), m) << "\n"; + ); + + expr* eq = eqs.get (i); + + // evaluate eq in M + app* a = to_app (eq); + expr_ref val (m); + m_mev.eval_array_eq (*M, a, a->get_arg (0), a->get_arg (1), val); + if (!val) { + // XXX HACK: unable to evaluate. set to true? + val = m.mk_true (); + } + SASSERT (m.is_true (val) || m.is_false (val)); + + if (m.is_false (val)) { + m_false_sub_v.insert (eq, m.mk_false ()); + } + else { + true_eqs.push_back (to_app (eq)); + } + } + + // 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 num_true_eqs = true_eqs.size (); + vector nds (num_true_eqs); + for (unsigned i = 0; i < num_true_eqs; i++) { + app* eq = true_eqs.get (i); + 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 = 0; + + SASSERT (lhs_has_v || rhs_has_v); + + if (!lhs_has_v) { + store = to_app (rhs); + } + else if (!rhs_has_v) { + store = to_app (lhs); + } + // else v appears on both sides -- trivial equality + // put it in the beginning to simplify it away + + unsigned nd = 0; // nesting depth + if (store) { + for (nd = 1; m_arr_u.is_store (store); + nd++, store = to_app (store->get_arg (0))) + /* empty */ ; + SASSERT (store == m_v); + } + nds[i] = nd; + } + + SASSERT (true_eqs.size () == nds.size ()); + + // sort true_eqs according to nesting depth + // use insertion sort + for (unsigned i = 1; i < num_true_eqs; i++) { + app_ref eq(m); + eq = true_eqs.get (i); + unsigned nd = nds.get (i); + unsigned j = i; + for (; j >= 1 && nds.get (j-1) > nd; j--) { + true_eqs.set (j, true_eqs.get (j-1)); + nds.set (j, nds.get (j-1)); + } + if (j < i) { + true_eqs.set (j, eq); + nds.set (j, nd); + TRACE ("qe", + tout << "changing eq order!\n"; + ); + } + } + + // search for subst term + for (unsigned i = 0; !m_subst_term_v && i < num_true_eqs; i++) { + app* eq = true_eqs.get (i); + 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 = m.mk_and (lits.size (), lits.c_ptr ()); + + 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), + m_mev (m) + {} + + void operator () (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { + reset (); + app_ref_vector rem_arr_vars (m); // remaining arr vars + M = &mdl; + + 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: " << mk_pp (m_v, m) << "\n"; + ); + aux_vars.push_back (m_v); + continue; + } + TRACE ("qe", + tout << "projecting equalities on variable: " << mk_pp (m_v, m) << "\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)) { + rem_arr_vars.push_back (m_v); + } + TRACE ("qe", + tout << "after projection: \n"; + tout << mk_pp (fml, m) << "\n"; + ); + } + else { + IF_VERBOSE(2, verbose_stream() << "can't project:" << mk_pp(m_v, m) << "\n";); + TRACE ("qe", tout << "Failed to project: " << mk_pp (m_v, m) << "\n";); + rem_arr_vars.push_back(m_v); + } + } + arr_vars.reset (); + arr_vars.append (rem_arr_vars); + aux_vars.append (m_aux_vars); + } + }; + + + 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_array_util 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 = 0; + m_arr_test.reset (); + m_has_stores.reset (); + m_reduce_all_selects = false; + } + + bool is_equals (expr *e1, expr *e2) { + if (e1 == e2) return true; + expr_ref val1 (m), val2 (m); + m_mev.eval (*M, e1, val1); + m_mev.eval (*M, e2, val2); + return (val1 == val2); + } + + 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 = 0; + if (m_cache.find (e, r)) { + e = r; + return true; + } + + ptr_vector todo; + todo.push_back (to_app (e)); + + while (!todo.empty ()) { + app *a = todo.back (); + unsigned sz = todo.size (); + expr_ref_vector args (m); + bool dirty = false; + bool args_have_stores = false; + + for (unsigned i = 0; i < a->get_num_args (); ++i) { + expr *arg = a->get_arg (i); + expr *narg = 0; + + 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; + } + + expr* reduce_core (app *a) { + if (!m_arr_u.is_store (a->get_arg (0))) return a; + + SASSERT (a->get_num_args () == 2 && "Multi-dimensional arrays are not supported"); + expr* array = a->get_arg (0); + expr* j = a->get_arg (1); + + while (m_arr_u.is_store (array)) { + a = to_app (array); + expr* idx = a->get_arg (1); + expr_ref cond (m); + + if (is_equals (idx, j)) { + cond = m.mk_eq (idx, j); + add_idx_cond (cond); + return a->get_arg (2); + } + else { + cond = m.mk_not (m.mk_eq (idx, j)); + add_idx_cond (cond); + array = a->get_arg (0); + } + } + + expr* args[2] = {array, j}; + expr* r = m_arr_u.mk_select (2, args); + 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 = m.mk_and (lits.size (), lits.c_ptr ()); + // simplify all trivial expressions introduced + m_rw (fml); + + TRACE ("qe", + tout << "after reducing selects:\n"; + tout << mk_pp (fml, m) << "\n"; + ); + } + + public: + + array_select_reducer (ast_manager& m): + m (m), + m_arr_u (m), + m_pinned (m), + m_idx_lits (m), + m_mev (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 (); + M = &mdl; + m_reduce_all_selects = reduce_all_selects; + + // mark vars to eliminate + for (unsigned i = 0; i < arr_vars.size (); i++) { + m_arr_test.mark (arr_vars.get (i), 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";); + } + } + }; + + class array_project_selects_util { + typedef obj_map*> sel_map; + + ast_manager& m; + array_util m_arr_u; + arith_util m_ari_u; + sel_map m_sel_terms; + // representative indices for eliminating selects + expr_ref_vector m_idx_reprs; + expr_ref_vector m_idx_vals; + app_ref_vector m_sel_consts; + expr_ref_vector m_idx_lits; + model_ref M; + model_evaluator_array_util m_mev; + expr_safe_replace m_sub; + ast_mark m_arr_test; + + void reset () { + m_sel_terms.reset (); + m_idx_reprs.reset (); + m_idx_vals.reset (); + m_sel_consts.reset (); + m_idx_lits.reset (); + M = 0; + 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)); + 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 (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); + } + } + done.mark (a, true); + } + } + + /** + * 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); + sort* idx_sort = get_array_domain (v_sort, 0); + + unsigned start = m_idx_reprs.size (); // append at the end + + for (unsigned i = 0; i < sel_terms.size (); i++) { + app* a = sel_terms.get (i); + expr* idx = a->get_arg (1); + expr_ref val (m); + m_mev.eval (*M, idx, val); + + bool is_new = true; + for (unsigned j = start; j < m_idx_vals.size (); j++) { + if (m_idx_vals.get (j) == val) { + // 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) + expr* repr = m_idx_reprs.get (j); + m_idx_lits.push_back (m.mk_eq (idx, repr)); + + is_new = false; + break; + } + } + if (is_new) { + // new repr, val, and sel const + m_idx_reprs.push_back (idx); + m_idx_vals.push_back (val); + 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 + m_mev.eval (*M, a, val); + M->register_decl (c->get_decl (), val); + } + } + + // sort reprs by their value and add a chain of strict inequalities + + unsigned num_reprs = m_idx_reprs.size () - start; + if (num_reprs == 0) return; + + SASSERT ((m_ari_u.is_real (idx_sort) || m_ari_u.is_int (idx_sort)) + && "Unsupported index sort: neither real nor int"); + + // using insertion sort + unsigned end = start + num_reprs; + for (unsigned i = start+1; i < end; i++) { + expr_ref repr(m), val(m); + repr = m_idx_reprs.get (i); + val = m_idx_vals.get (i); + unsigned j = i; + for (; j > start; j--) { + rational j_val, jm1_val; + VERIFY (m_ari_u.is_numeral (val, j_val)); + VERIFY (m_ari_u.is_numeral (m_idx_vals.get (j-1), jm1_val)); + if (j_val >= jm1_val) break; + m_idx_reprs[j] = m_idx_reprs.get (j-1); + m_idx_vals[j] = m_idx_vals.get (j-1); + } + m_idx_reprs[j] = repr; + m_idx_vals[j] = val; + } + + for (unsigned i = start; i < end-1; i++) { + m_idx_lits.push_back (m_ari_u.mk_lt (m_idx_reprs.get (i), + m_idx_reprs.get (i+1))); + } + } + + void mk_result (expr_ref& fml) { + // conjoin idx lits + expr_ref_vector lits (m); + lits.append (m_idx_lits); + lits.push_back (fml); + fml = m.mk_and (lits.size (), lits.c_ptr ()); + + // substitute for sel terms + m_sub (fml); + + TRACE ("qe", + tout << "after projection of selects:\n"; + tout << mk_pp (fml, m) << "\n"; + ); + } + + /** + * project selects + * populates idx lits and obtains substitution for sel terms + */ + bool project (expr_ref& fml) { + // collect sel terms -- populate the map m_sel_terms + collect_selects (fml); + + // model based ackermannization + sel_map::iterator begin = m_sel_terms.begin (), + end = m_sel_terms.end (); + for (sel_map::iterator it = begin; it != end; it++) { + TRACE ("qe", + tout << "ackermann for var: " << mk_pp (it->m_key, m) << "\n"; + ); + ackermann (*(it->m_value)); + } + + TRACE ("qe", + tout << "idx lits:\n"; + for (unsigned i = 0; i < m_idx_lits.size (); i++) { + tout << mk_pp (m_idx_lits.get (i), m) << "\n"; + } + ); + + return true; + } + + public: + + array_project_selects_util (ast_manager& m): + m (m), + m_arr_u (m), + m_ari_u (m), + m_idx_reprs (m), + m_idx_vals (m), + m_sel_consts (m), + m_idx_lits (m), + m_mev (m), + m_sub (m) + {} + + void operator () (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { + reset (); + M = &mdl; + + // mark vars to eliminate + for (unsigned i = 0; i < arr_vars.size (); i++) { + m_arr_test.mark (arr_vars.get (i), true); + } + + // alloc empty map from array var to sel terms over it + for (unsigned i = 0; i < arr_vars.size (); i++) { + ptr_vector* lst = alloc (ptr_vector); + m_sel_terms.insert (arr_vars.get (i), lst); + } + + // 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 + sel_map::iterator begin = m_sel_terms.begin (), + end = m_sel_terms.end (); + for (sel_map::iterator it = begin; it != end; it++) { + dealloc (it->m_value); + } + m_sel_terms.reset (); + } + }; + + expr_ref arith_project(model& mdl, app_ref_vector& vars, expr_ref_vector const& lits) { + ast_manager& m = vars.get_manager(); + arith_project_util ap(m); + return ap(mdl, vars, lits); + } + + 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; + is_relevant_default is_relevant; + mk_atom_default mk_atom; + get_nnf (fml, is_relevant, mk_atom, pos_lits, neg_lits); + ap(mdl, vars, fml); + } + + 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; + is_relevant_default is_relevant; + mk_atom_default mk_atom; + get_nnf (fml, is_relevant, mk_atom, pos_lits, neg_lits); + ap(mdl, vars, fml, map); + } + + void array_project_eqs (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { + ast_manager& m = arr_vars.get_manager (); + array_project_eqs_util ap (m); + ap (mdl, arr_vars, fml, aux_vars); + } + + void reduce_array_selects (model& mdl, app_ref_vector const& arr_vars, expr_ref& fml, bool reduce_all_selects) { + ast_manager& m = arr_vars.get_manager (); + array_select_reducer ap (m); + ap (mdl, arr_vars, fml, reduce_all_selects); + } + + void reduce_array_selects (model& mdl, expr_ref& fml) { + ast_manager& m = fml.get_manager (); + app_ref_vector _tmp (m); + reduce_array_selects (mdl, _tmp, fml, true); + } + + void array_project_selects (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars) { + ast_manager& m = arr_vars.get_manager (); + array_project_selects_util ap (m); + ap (mdl, arr_vars, fml, aux_vars); + } + + void array_project (model& mdl, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects) { + // 1. project array equalities + array_project_eqs (mdl, arr_vars, fml, aux_vars); + TRACE ("qe", + ast_manager& m = fml.get_manager (); + tout << "Projected array eqs:\n" << mk_pp (fml, m) << "\n"; + tout << "Remaining array vars:\n"; + for (unsigned i = 0; i < arr_vars.size (); i++) { + tout << mk_pp (arr_vars.get (i), m) << "\n"; + } + tout << "Aux vars:\n"; + for (unsigned i = 0; i < aux_vars.size (); i++) { + tout << mk_pp (aux_vars.get (i), m) << "\n"; + } + ); + + // 2. reduce selects + if (reduce_all_selects) { + reduce_array_selects (mdl, fml); + } + else { + reduce_array_selects (mdl, arr_vars, fml); + } + TRACE ("qe", + ast_manager& m = fml.get_manager (); + tout << "Reduced selects:\n" << mk_pp (fml, m) << "\n"; + ); + + // 3. project selects using model based ackermannization + array_project_selects (mdl, arr_vars, fml, aux_vars); + TRACE ("qe", + ast_manager& m = fml.get_manager (); + tout << "Projected array selects:\n" << mk_pp (fml, m) << "\n"; + tout << "All aux vars:\n"; + for (unsigned i = 0; i < aux_vars.size (); i++) { + tout << mk_pp (aux_vars.get (i), m) << "\n"; + } + ); + } + +} diff --git a/src/muz/spacer/spacer_qe_project.h b/src/muz/spacer/spacer_qe_project.h new file mode 100644 index 000000000..6074f0d91 --- /dev/null +++ b/src/muz/spacer/spacer_qe_project.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_qe_project.h + +Abstract: + + Model-based projection + +Author: + + Anvesh Komuravelli + Arie Gurfinkel (arie) + +Notes: + +--*/ +#ifndef SPACER_QE_PROJECT_H_ +#define SPACER_QE_PROJECT_H_ + +#include "model.h" +#include "expr_map.h" + +namespace qe { + /** + Loos-Weispfenning model-based projection for a basic conjunction. + Lits is a vector of literals. + return vector of variables that could not be projected. + */ + expr_ref arith_project(model& model, app_ref_vector& vars, expr_ref_vector const& lits); + + void arith_project(model& model, app_ref_vector& vars, expr_ref& fml); + + void arith_project(model& model, app_ref_vector& vars, expr_ref& fml, expr_map& map); + + void array_project_eqs (model& model, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars); + + void reduce_array_selects (model& mdl, app_ref_vector const& arr_vars, expr_ref& fml, bool reduce_all_selects = false); + + void reduce_array_selects (model& mdl, expr_ref& fml); + + void array_project_selects (model& model, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars); + + void array_project (model& model, app_ref_vector& arr_vars, expr_ref& fml, app_ref_vector& aux_vars, bool reduce_all_selects = false); +}; + +#endif diff --git a/src/muz/spacer/spacer_smt_context_manager.cpp b/src/muz/spacer/spacer_smt_context_manager.cpp new file mode 100644 index 000000000..f5e5ba9c1 --- /dev/null +++ b/src/muz/spacer/spacer_smt_context_manager.cpp @@ -0,0 +1,79 @@ +/*++ +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 "spacer_smt_context_manager.h" +#include "has_free_vars.h" +#include "ast_pp.h" +#include "ast_smt_pp.h" +#include +#include "smt_params.h" + +#include "ast_pp_util.h" +#include "smt_context.h" +#include "spacer_util.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 = 0; + + 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 new file mode 100644 index 000000000..fd04eaf4e --- /dev/null +++ b/src/muz/spacer/spacer_smt_context_manager.h @@ -0,0 +1,68 @@ +/*++ +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 "smt_kernel.h" +#include "func_decl_dependencies.h" +#include "dl_util.h" +#include "spacer_virtual_solver.h" +#include "stopwatch.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 new file mode 100644 index 000000000..659aea2cc --- /dev/null +++ b/src/muz/spacer/spacer_sym_mux.cpp @@ -0,0 +1,608 @@ +/*++ +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_pp.h" +#include "for_each_expr.h" +#include "model.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "spacer_util.h" +#include "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) +{ + unsigned 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) 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]; +} + +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++) { + + 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); +} + +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); + } +} + +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]; +} + + +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; + const sym_mux & m_parent; + unsigned m_from_idx; + unsigned m_to_idx; + bool m_homogenous; +public: + 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) {} + + 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)) { + 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) 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::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 new file mode 100644 index 000000000..d9bae95a3 --- /dev/null +++ b/src/muz/spacer/spacer_sym_mux.h @@ -0,0 +1,256 @@ +/*++ +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.h" +#include "map.h" +#include "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; + + 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 */ + mutable 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) 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); + + 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 0; + } + 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) const + { + func_decl * prim = try_get_primary_by_prefix(prefix); + if(!prim) { + return 0; + } + 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) const; + + + /** + 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) const; + void conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, + expr_ref_vector & res) const; + + /** + 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) 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; +}; +} + +#endif diff --git a/src/muz/spacer/spacer_unsat_core_learner.cpp b/src/muz/spacer/spacer_unsat_core_learner.cpp new file mode 100644 index 000000000..1e4dc522f --- /dev/null +++ b/src/muz/spacer/spacer_unsat_core_learner.cpp @@ -0,0 +1,360 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_unsat_core_learner.cpp + +Abstract: + itp cores + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#include "spacer_unsat_core_learner.h" + +#include "spacer_unsat_core_plugin.h" + +#include "proof_utils.h" +#include "for_each_expr.h" +#include +namespace spacer +{ + +#pragma mark - proof iterators + +# pragma mark - main methods +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) +{ + 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); + + spacer::reduce_hypotheses(pr); + STRACE("spacer.unsat_core_learner", + verbose_stream() << "Reduced proof:\n" << mk_ismt2_pp(pr, m) << "\n"; + ); + + // compute symbols occuring in B + collect_symbols_b(asserted_b); + + // traverse proof + ProofIteratorPostOrder it(root, 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; + 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)); + } + + // 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); + 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)) + { + 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! + } + } + + // give plugins chance to finalize their unsat-core-computation + finalize(); + + // 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; + + ProofIteratorPostOrder 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); + } +} + +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; + 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; + plugin->finalize(); + } +} + +#pragma mark - API + +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) +{ + return m_closed.is_marked(p); +} +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); + } + +# pragma mark - checking for b_symbols + +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(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); + } +} + +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; +} + + + +} diff --git a/src/muz/spacer/spacer_unsat_core_learner.h b/src/muz/spacer/spacer_unsat_core_learner.h new file mode 100644 index 000000000..91ae01292 --- /dev/null +++ b/src/muz/spacer/spacer_unsat_core_learner.h @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_unsat_core_learner.h + +Abstract: + itp cores + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#ifndef _SPACER_UNSAT_CORE_LEARNER_H_ +#define _SPACER_UNSAT_CORE_LEARNER_H_ + +#include "ast.h" +#include "spacer_util.h" +#include "spacer_proof_utils.h" + +namespace spacer { + + + class unsat_core_plugin; + class unsat_core_learner + { + typedef obj_hashtable expr_set; + + public: + unsat_core_learner(ast_manager& m) : m(m), m_unsat_core(m) {}; + virtual ~unsat_core_learner(); + + ast_manager& m; + + /* + * register a plugin for computation of partial unsat cores + * currently plugins are called in the order they have been registered + */ + void register_plugin(unsat_core_plugin* plugin); + + /* + * compute unsat core using the registered unsat-core-plugins + */ + void compute_unsat_core(proof* root, expr_set& asserted_b, 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 + * - 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); + + /* + * 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(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. + + /* + * computes partial core for step by delegating computation to plugins + */ + void compute_partial_core(proof* step); + + /* + * finalize computation of unsat-core + */ + void finalize(); + }; + +} + +#endif diff --git a/src/muz/spacer/spacer_unsat_core_plugin.cpp b/src/muz/spacer/spacer_unsat_core_plugin.cpp new file mode 100644 index 000000000..d0d443528 --- /dev/null +++ b/src/muz/spacer/spacer_unsat_core_plugin.cpp @@ -0,0 +1,776 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_unsat_core_plugin.cpp + +Abstract: + plugin for itp cores + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#include "spacer_unsat_core_plugin.h" + +#include "spacer_unsat_core_learner.h" + +#include "smt_farkas_util.h" +#include "bool_rewriter.h" +#include "arith_decl_plugin.h" +#include +#include "smt_solver.h" +#include "solver.h" +#include +#include "spacer_proof_utils.h" +#include "spacer_matrix.h" + +namespace spacer +{ + +#pragma mark - unsat_core_plugin_lemma + +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) + { + 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); + } + } + 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()) + { + 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); + } + // 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); + } + } + } + + } + } +} + + +#pragma mark - unsat_core_plugin_farkas_lemma +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)); + } + } + } + + 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 + + // 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)); + } + } + } + + // 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); + } + } +} + +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); + } +} + +#pragma mark - unsat_core_plugin_farkas_optimized + 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)); + + 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)); + + vector > 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"; + } + ); + + 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); + + if (m_learner.is_b_marked(premise) && !m_learner.is_closed(premise)) + { + SASSERT(!m_learner.is_a_marked(premise)); + + // XXX AG: why is this condition is based on step and not on premise? + if (m_learner.only_contains_symbols_b(m_learner.m.get_fact(step)) && !m_learner.is_h_marked(step)) + { + 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))); + } + else + { + can_be_closed = false; + } + } + } + + // only if all b-premises can be used directly, close the step and add linear combinations for later processing + if (can_be_closed) + { + m_learner.set_closed(step, true); + if (!linear_combination.empty()) + { + m_linear_combinations.push_back(linear_combination); + } + } + } + } + + struct farkas_optimized_less_than_pairs + { + inline bool operator() (const std::pair& pair1, const std::pair& pair2) const + { + return (pair1.first->get_id() < pair2.first->get_id()); + } + }; + + void unsat_core_plugin_farkas_lemma_optimized::finalize() + { + 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? + } + +#pragma mark - unsat_core_plugin_farkas_bounded + + 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); + }); + + // 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); + } + } + matrix.print_matrix(); + + // 3. normalize matrix to integer values + matrix.normalize(); + + + arith_util util(m); + + vector coeffs; + 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) + { + bounded_vectors.push_back(expr_ref_vector(m)); + } + + // 4. find smallest n using guess and check algorithm + 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? + + // add new variables w_in, + 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*)0, util.mk_int()); + coeffs[i].push_back(m.mk_const(decl)); + } + + // we need s_jn + 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*)0, util.mk_int()); + + expr_ref s_jn(m); + s_jn = m.mk_const(decl); + + bounded_vectors[j].push_back(s_jn); + } + + // assert bounds for all s_jn + 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) + { + 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 (int 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); + } + } + + // check result + lbool res = s->check_sat(0,0); + + // if sat extract model and add corresponding linear combinations to core + if (res == lbool::l_true) + { + model_ref model; + s->get_model(model); + + for (int k=0; k < n; ++k) + { + ptr_vector literals; + vector coefficients; + for (int j=0; j < matrix.num_cols(); ++j) + { + expr_ref evaluation(m); + + model.get()->eval(bounded_vectors[j][k].get(), evaluation, false); + if (!util.is_zero(evaluation)) + { + literals.push_back(ordered_basis[j]); + coefficients.push_back(rational(1)); + } + } + 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); + + m_learner.add_lemma_to_core(linear_combination); + } + return; + } + } + } + +#pragma mark - unsat_core_plugin_min_cut + unsat_core_plugin_min_cut::unsat_core_plugin_min_cut(unsat_core_learner& learner, ast_manager& m) : unsat_core_plugin(learner), m(m){} + + void unsat_core_plugin_min_cut::compute_partial_core(proof* step) + { + ptr_vector todo; + + SASSERT(m_learner.is_a_marked(step)); + SASSERT(m_learner.is_b_marked(step)); + SASSERT(m.get_num_parents(step) > 0); + SASSERT(!m_learner.is_closed(step)); + todo.push_back(step); + + while (!todo.empty()) + { + proof* current = todo.back(); + todo.pop_back(); + + if (!m_learner.is_closed(current) && !m_visited.is_marked(current)) + { + m_visited.mark(current, true); + advance_to_lowest_partial_cut(current, todo); + } + } + m_learner.set_closed(step, true); + } + + void unsat_core_plugin_min_cut::advance_to_lowest_partial_cut(proof* step, ptr_vector& todo2) + { + bool is_sink = true; + + ast_manager &m = m_learner.m; + ptr_vector todo; + + 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.push_back(premise); + } + } + } + while (!todo.empty()) + { + proof* current = todo.back(); + todo.pop_back(); + + // if current step hasn't been processed, + if (!m_learner.is_closed(current)) + { + SASSERT(!m_learner.is_a_marked(current)); // by I.H. the step must be already visited + + // and the current step needs to be interpolated: + if (m_learner.is_b_marked(current)) + { + // if we trust the current step and we are able to use it + if (m_learner.is_b_pure (current) && + (m.is_asserted(current) || + is_literal(m, m.get_fact(current)))) + { + // add corresponding edges and continue original traversel + if (m_learner.is_a_marked(step)) + { + add_edge(nullptr, current); // current is sink + } + else + { + add_edge(step, current); + } + todo2.push_back(current); + is_sink = false; + } + // otherwise recurse on premises + else + { + for (unsigned i = 0; i < m_learner.m.get_num_parents(current); ++i) + { + SASSERT(m_learner.m.is_proof(current->get_arg(i))); + proof* premise = m.get_parent (current, i); + todo.push_back(premise); + } + } + } + } + } + + if (is_sink) + { + add_edge(step, nullptr); + } + } + + void unsat_core_plugin_min_cut::add_edge(proof* i, proof* j) + { + unsigned node_i; + unsigned node_j; + if (i == nullptr) + { + node_i = 0; + } + else + { + unsigned tmp; + if (m_proof_to_node_plus.find(i, tmp)) + { + node_i = tmp; + } + else + { + unsigned node_other = m_min_cut.new_node(); + node_i = m_min_cut.new_node(); + + m_proof_to_node_minus.insert(i, node_other); + m_proof_to_node_plus.insert(i, node_i); + + if (node_i >= m_node_to_formula.size()) + { + m_node_to_formula.resize(node_i + 1); + } + m_node_to_formula[node_other] = m.get_fact(i); + m_node_to_formula[node_i] = m.get_fact(i); + + m_min_cut.add_edge(node_other, node_i, 1); + } + } + + if (j == nullptr) + { + node_j = 1; + } + else + { + unsigned tmp; + if (m_proof_to_node_minus.find(j, tmp)) + { + node_j = tmp; + } + else + { + node_j = m_min_cut.new_node(); + unsigned node_other = m_min_cut.new_node(); + + m_proof_to_node_minus.insert(j, node_j); + m_proof_to_node_plus.insert(j, node_other); + + if (node_other >= m_node_to_formula.size()) + { + m_node_to_formula.resize(node_other + 1); + } + m_node_to_formula[node_j] = m.get_fact(j); + m_node_to_formula[node_other] = m.get_fact(j); + + m_min_cut.add_edge(node_j, node_other, 1); + } + } + + // finally connect nodes + m_min_cut.add_edge(node_i, node_j, 1); + } + + void unsat_core_plugin_min_cut::finalize() + { + vector cut_nodes; + m_min_cut.compute_min_cut(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 new file mode 100644 index 000000000..6e1f383c1 --- /dev/null +++ b/src/muz/spacer/spacer_unsat_core_plugin.h @@ -0,0 +1,115 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + spacer_unsat_core_plugin.h + +Abstract: + plugin for itp cores + +Author: + Bernhard Gleiss + +Revision History: + + +--*/ +#ifndef _SPACER_UNSAT_CORE_PLUGIN_H_ +#define _SPACER_UNSAT_CORE_PLUGIN_H_ + +#include "ast.h" +#include "spacer_min_cut.h" + +namespace spacer { + +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){}; + + virtual 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) {}; + + virtual 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) {}; + + virtual void compute_partial_core(proof* step) override; + virtual void finalize() override; + + protected: + vector > > m_linear_combinations; + ast_manager& m; + /* + * 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_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) {}; + + virtual 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); + + virtual void compute_partial_core(proof* step) override; + virtual 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) + void advance_to_lowest_partial_cut(proof* step, ptr_vector& todo2); + void add_edge(proof* i, proof* j); + + vector m_node_to_formula; // maps each node to the corresponding formula in the original proof + + spacer_min_cut m_min_cut; + }; +} +#endif diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp new file mode 100644 index 000000000..a2cf4101f --- /dev/null +++ b/src/muz/spacer/spacer_util.cpp @@ -0,0 +1,1393 @@ +/** +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_util.cpp + +Abstract: + + Utility functions for SPACER. + +Author: + + Krystof Hoder (t-khoder) 2011-8-19. + Arie Gurfinkel + Anvesh Komuravelli + +Revision History: + + Modified by Anvesh Komuravelli + +Notes: + + +--*/ + +#include +#include + +#include "ast.h" +#include "occurs.h" +#include "array_decl_plugin.h" +#include "ast_pp.h" +#include "bool_rewriter.h" +#include "dl_util.h" +#include "for_each_expr.h" +#include "smt_params.h" +#include "model.h" +#include "model_evaluator.h" +#include "ref_vector.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "util.h" +#include "spacer_manager.h" +#include "spacer_util.h" +#include "arith_decl_plugin.h" +#include "expr_replacer.h" +#include "model_smt2_pp.h" +#include "scoped_proof.h" +#include "qe_lite.h" +#include "spacer_qe_project.h" +#include "model_pp.h" +#include "expr_safe_replace.h" + +#include "datatype_decl_plugin.h" +#include "bv_decl_plugin.h" + +#include "spacer_legacy_mev.h" +#include "qe_mbp.h" + +#include "tactical.h" +#include "propagate_values_tactic.h" +#include "propagate_ineqs_tactic.h" +#include "arith_bounds_tactic.h" + +#include "obj_equiv_class.h" + +namespace spacer { + + ///////////////////////// + // model_evaluator_util + // + + model_evaluator_util::model_evaluator_util(ast_manager& m) : + m(m), m_mev(NULL) + { reset (NULL); } + + model_evaluator_util::~model_evaluator_util() {reset (NULL);} + + +void model_evaluator_util::reset(model* model) +{ + if (m_mev) { + dealloc(m_mev); + m_mev = NULL; + } + 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); + } +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()); + 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()); + } + + // + // (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); + params_ref p; + ite_hoister_star ite_rw(m, p); + expr_ref tmp(m); + ite_rw(fml, tmp); + fml = tmp; + } + + 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); + } + + /* + * 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) +{ + 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"; + } + ); + + { + // 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()); } + } + + app_ref_vector arith_vars (m); + app_ref_vector array_vars (m); + array_util arr_u (m); + arith_util ari_u (m); + expr_safe_replace bool_sub (m); + expr_ref bval (m); + + while (true) { + 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"; + } + ); + 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)); + } + } + + // substitute Booleans + if (has_bool_vars) { + 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 (); + } + + 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"; + } + ); + + vars.reset (); + + // project arrays + { + 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); + 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"; + } + ); + + 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";); + + 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 ()); + } else { + scoped_no_proof _sp (m); + qe::arith_project (*M.get (), 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"; + } + ); + SASSERT (!m.is_false (fml)); + } + + if (!arith_vars.empty ()) { + mbqi_project (*M.get(), arith_vars, fml); + } + + // substitute any remaining arith vars + if (!dont_sub && !arith_vars.empty ()) { + subst_vars (m, arith_vars, M.get(), fml); + TRACE ("spacer_mbp", + tout << "After substituting remaining arith vars:\n"; + tout << mk_pp (fml, m) << "\n"; + ); + // an extra round of simplification because subst_vars is not simplifying + rw(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)); + ); + + vars.reset (); + if (dont_sub && !arith_vars.empty ()) + { vars.append(arith_vars); } + } + + + static expr* apply_accessor(ast_manager &m, + 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(ast_manager &m, expr_ref_vector& conjs) +{ + if (conjs.empty()) { return; } + arith_util arith(m); + datatype_util dt(m); + bv_util bv(m); + expr* e1, *e2, *c, *val; + 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"; + }); + + 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_recognizer(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(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))) { + 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("spacer_expand", + tout << "end expand\n"; + for (unsigned i = 0; i < conjs.size(); ++i) { + tout << mk_pp(conjs[i].get(), m) << "\n"; + }); + } + +namespace { +class implicant_picker { + model_evaluator_util &m_mev; + ast_manager &m; + arith_util m_arith; + + expr_ref_vector m_todo; + expr_mark m_visited; + + + void add_literal (expr *e, expr_ref_vector &out) + { + SASSERT (m.is_bool (e)); + + 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; + + 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); + } + } + + expr *nres, *f1, *f2; + if (m.is_not(res, nres)) { + // -- (not (xor a b)) == (= a b) + if (m.is_xor(nres, 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); } + } + } + + 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); + } + + 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); + + if (!m.is_true(v) && !m.is_false(v)) { return; } + + expr *na, *f1, *f2, *f3; + + 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); } + 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 + 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)); + 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)); + 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))) { + 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()); } + else + { 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_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 */ } + else { + verbose_stream () << "Unexpected expression: " + << mk_pp(a, m) << "\n"; + UNREACHABLE(); + } + } + + void pick_literals(expr *e, expr_ref_vector &out) + { + SASSERT(m_todo.empty()); + if (m_visited.is_marked(e)) { return; } + SASSERT(is_app(e)); + + m_todo.push_back(e); + do { + app *a = to_app(m_todo.back()); + m_todo.pop_back(); + process_app(a, out); + m_visited.mark(a, true); + } while (!m_todo.empty()); + } + + 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); + + 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); } + } + + 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);} + }; + } + + 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); + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref core(m); + goal_ref_buffer result; + tactic_ref simplifier = mk_arith_bounds_tactic(m); + (*simplifier)(g, result, mc, pc, core); + 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(); + + + 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)); + } + + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref dep(m); + 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()); + + (*t)(g, goals, mc, pc, dep); + SASSERT(goals.size() == 1); + + g = goals[0]; + cube.reset(); + for (unsigned i = 0; i < g->size(); ++i) { + cube.push_back(g->form(i)); + } +} + +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 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); } + + 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; + } + } + + 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; + + 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();} + + 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); + return BR_DONE; + } + // simplify normalized leq, where right side is different from 0 + // 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)) { + expr_ref rhs(m); + rhs = is_zero (args[1]) ? e4 : m_arith.mk_add(e4, args[1]); + + if (is_le(f)) { + result = m_arith.mk_le(e1, rhs); + st = BR_DONE; + } else if (is_lt(f)) { + result = m_arith.mk_lt(e1, rhs); + st = BR_DONE; + } else if (is_ge(f)) { + result = m_arith.mk_ge(e1, rhs); + st = BR_DONE; + } else if (is_gt(f)) { + result = m_arith.mk_gt(e1, rhs); + st = BR_DONE; + } else + { UNREACHABLE(); } + } + // simplify negation of ordering predicate + 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; + } else if (m_arith.is_le(args[0], e1, e2)) { + result = m_arith.mk_gt(e1, e2); + st = BR_DONE; + } else if (m_arith.is_gt(args[0], e1, e2)) { + result = m_arith.mk_le(e1, e2); + st = BR_DONE; + } else if (m_arith.is_ge(args[0], e1, e2)) { + result = m_arith.mk_lt(e1, e2); + st = BR_DONE; + } + } + 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); + + 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); + } + + + 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) && 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); } + } + } + }; + + bool mbqi_project_var (model_evaluator_util &mev, app* var, expr_ref &fml) + { + ast_manager &m = fml.get_manager (); + + expr_ref val(m); + mev.eval (var, val, false); + + 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 << "terms:\n"; + for (unsigned i = 0, e = terms.size (); i < e; ++i) + tout << i << ": " << mk_pp (terms.get (i), m) << "\n"; + ); + + 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";); + + // -- 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 " << mk_pp (fml, m) << "\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); + expr_ref tmp(m); + // -- evaluate to initialize mev cache + mev.eval (fml, tmp, false); + 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++; + } + } + } + +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); + } + return false; + } + +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); + } + } + +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 new file mode 100644 index 000000000..f51df7a7b --- /dev/null +++ b/src/muz/spacer/spacer_util.h @@ -0,0 +1,180 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + spacer_util.h + +Abstract: + + Utility functions for SPACER. + +Author: + + Krystof Hoder (t-khoder) 2011-8-19. + Arie Gurfinkel + Anvesh Komuravelli + +Revision History: + +--*/ + +#ifndef _SPACER_UTIL_H_ +#define _SPACER_UTIL_H_ + +#include "ast.h" +#include "ast_pp.h" +#include "obj_hashtable.h" +#include "ref_vector.h" +#include "simplifier.h" +#include "trace.h" +#include "vector.h" +#include "arith_decl_plugin.h" +#include "array_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "model.h" + +#include "stopwatch.h" +#include "spacer_antiunify.h" + +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; + } +} + + +struct scoped_watch { + stopwatch &m_sw; + scoped_watch (stopwatch &sw, bool reset=false): m_sw(sw) + { + if(reset) { m_sw.reset(); } + m_sw.start (); + } + ~scoped_watch () {m_sw.stop ();} +}; + + +typedef ptr_vector app_vector; +typedef ptr_vector decl_vector; +typedef obj_hashtable func_decl_set; + + +class model_evaluator_util { + ast_manager& m; + model_ref m_model; + model_evaluator* m_mev; + + /// 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(); + + void set_model(model &model) {reset (&model);} + model_ref &get_model() {return m_model;} + ast_manager& get_ast_manager() const {return m;} + +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; + mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = 0); + 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 new file mode 100644 index 000000000..c6b85a3ea --- /dev/null +++ b/src/muz/spacer/spacer_virtual_solver.cpp @@ -0,0 +1,519 @@ +/** +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 "spacer_virtual_solver.h" +#include "ast_util.h" +#include "ast_pp_util.h" +#include "spacer_util.h" +#include "bool_rewriter.h" + +#include "proof_checker.h" + +#include "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(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 { +static bool matches_fact(expr_ref_vector &args, expr* &match) +{ + ast_manager &m = args.get_manager(); + expr *fact = args.back(); + for (unsigned i = 0, sz = args.size() - 1; i < sz; ++i) { + expr *arg = args.get(i); + if (m.is_proof(arg) && + m.has_fact(to_app(arg)) && + m.get_fact(to_app(arg)) == fact) { + match = arg; + return true; + } + } + return false; +} + + +class elim_aux_assertions { + app_ref m_aux; +public: + elim_aux_assertions(app_ref aux) : m_aux(aux) {} + + void mk_or_core(expr_ref_vector &args, expr_ref &res) + { + ast_manager &m = args.get_manager(); + unsigned j = 0; + for (unsigned i = 0, sz = args.size(); i < sz; ++i) { + if (m.is_false(args.get(i))) { continue; } + if (i != j) { args [j] = args.get(i); } + ++j; + } + SASSERT(j >= 1); + res = j > 1 ? m.mk_or(j, args.c_ptr()) : args.get(0); + } + + void mk_app(func_decl *decl, expr_ref_vector &args, expr_ref &res) + { + ast_manager &m = args.get_manager(); + bool_rewriter brwr(m); + + if (m.is_or(decl)) + { mk_or_core(args, res); } + else if (m.is_iff(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)); } + else + { brwr.mk_app(decl, args.size(), args.c_ptr(), res); } + } + + void operator()(ast_manager &m, proof *pr, proof_ref &res) + { + DEBUG_CODE(proof_checker pc(m); + expr_ref_vector side(m); + SASSERT(pc.check(pr, side)); + ); + obj_map cache; + bool_rewriter brwr(m); + + // for reference counting of new proofs + app_ref_vector pinned(m); + + ptr_vector todo; + todo.push_back(pr); + + expr_ref not_aux(m); + not_aux = m.mk_not(m_aux); + + expr_ref_vector args(m); + + while (!todo.empty()) { + app *p, *r; + expr *a; + + p = todo.back(); + if (cache.find(pr, r)) { + todo.pop_back(); + continue; + } + + SASSERT(!todo.empty() || pr == p); + bool dirty = false; + unsigned todo_sz = todo.size(); + args.reset(); + for (unsigned i = 0, sz = p->get_num_args(); i < sz; ++i) { + expr* arg = p->get_arg(i); + if (arg == m_aux.get()) { + dirty = true; + args.push_back(m.mk_true()); + } else if (arg == not_aux.get()) { + dirty = true; + args.push_back(m.mk_false()); + } + // skip (asserted m_aux) + else if (m.is_asserted(arg, a) && a == m_aux.get()) { + dirty = true; + } + // skip (hypothesis m_aux) + else if (m.is_hypothesis(arg, a) && a == m_aux.get()) { + dirty = true; + } else if (is_app(arg) && cache.find(to_app(arg), r)) { + dirty |= (arg != r); + args.push_back(r); + } else if (is_app(arg)) + { todo.push_back(to_app(arg)); } + else + // -- not an app + { args.push_back(arg); } + + } + if (todo_sz < todo.size()) { + // -- process parents + args.reset(); + continue; + } + + // ready to re-create + app_ref newp(m); + if (!dirty) { newp = p; } + else if (m.is_unit_resolution(p)) { + if (args.size() == 2) + // unit resolution with m_aux that got collapsed to nothing + { newp = to_app(args.get(0)); } + else { + ptr_vector parents; + for (unsigned i = 0, sz = args.size() - 1; i < sz; ++i) + { parents.push_back(to_app(args.get(i))); } + SASSERT(parents.size() == args.size() - 1); + newp = m.mk_unit_resolution(parents.size(), parents.c_ptr()); + // XXX the old and new facts should be + // equivalent. The test here is much + // stronger. It might need to be relaxed. + SASSERT(m.get_fact(newp) == args.back()); + pinned.push_back(newp); + } + } else if (matches_fact(args, a)) { + newp = to_app(a); + } else { + expr_ref papp(m); + mk_app(p->get_decl(), args, papp); + newp = to_app(papp.get()); + pinned.push_back(newp); + } + cache.insert(p, newp); + todo.pop_back(); + CTRACE("virtual", + p->get_decl_kind() == PR_TH_LEMMA && + p->get_decl()->get_parameter(0).get_symbol() == "arith" && + p->get_decl()->get_num_parameters() > 1 && + p->get_decl()->get_parameter(1).get_symbol() == "farkas", + tout << "Old pf: " << mk_pp(p, m) << "\n" + << "New pf: " << mk_pp(newp, m) << "\n";); + } + + proof *r; + VERIFY(cache.find(pr, r)); + + DEBUG_CODE( + proof_checker pc(m); + expr_ref_vector side(m); + SASSERT(pc.check(r, side)); + ); + + res = r ; + } +}; +} + +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_formulas()[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(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(0, tmp); + r.append(tmp.size(), tmp.c_ptr()); +} + +solver* virtual_solver::translate(ast_manager& m, params_ref const& p) +{ + UNREACHABLE(); + return 0; +} +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); } +bool virtual_solver::get_produce_models() +{return m_factory.get_produce_models(); } +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_formulas()[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 new file mode 100644 index 000000000..b5e97dce0 --- /dev/null +++ b/src/muz/spacer/spacer_virtual_solver.h @@ -0,0 +1,153 @@ +/** +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.h" +#include"params.h" +#include"solver_na2as.h" +#include"smt_kernel.h" +#include"smt_params.h" +#include"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 ~virtual_solver(); + virtual unsigned get_num_assumptions() const + { + unsigned sz = solver_na2as::get_num_assumptions(); + return m_virtual ? sz - 1 : sz; + } + virtual expr* get_assumption(unsigned idx) const + { + if(m_virtual) { idx++; } + return solver_na2as::get_assumption(idx); + } + + virtual void get_unsat_core(ptr_vector &r); + virtual void assert_expr(expr *e); + virtual void collect_statistics(statistics &st) const {} + virtual void get_model(model_ref &m) {m_context.get_model(m);} + virtual proof* get_proof(); + virtual std::string reason_unknown() const + {return m_context.last_failure_as_string();} + virtual void set_reason_unknown(char const *msg) + {m_context.set_reason_unknown(msg);} + virtual ast_manager& get_manager() const {return m;} + virtual void get_labels(svector &r); + virtual void set_produce_models(bool f); + virtual bool get_produce_models(); + virtual smt_params &fparams(); + virtual void reset(); + + virtual void set_progress_callback(progress_callback *callback) + {UNREACHABLE();} + + virtual solver *translate(ast_manager &m, params_ref const &p); + + virtual void updt_params(params_ref const &p); + virtual void collect_param_descrs(param_descrs &r); + + +protected: + virtual lbool check_sat_core(unsigned num_assumptions, expr *const * assumptions); + virtual void push_core(); + virtual void pop_core(unsigned n); +}; + +/// 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(); +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; } + smt_params &fparams() { return m_fparams; } +}; + +} + + +#endif From a73023da97426cea00168657af3bd0ab4b315559 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:38:48 -0400 Subject: [PATCH 05/17] preserve rule names when changing rules --- src/muz/base/dl_rule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/base/dl_rule.cpp b/src/muz/base/dl_rule.cpp index 4581c8aac..af48d0f8b 100644 --- a/src/muz/base/dl_rule.cpp +++ b/src/muz/base/dl_rule.cpp @@ -639,7 +639,7 @@ namespace datalog { tail.push_back(ensure_app(conjs[i].get())); } tail_neg.resize(tail.size(), false); - r = mk(r->get_head(), tail.size(), tail.c_ptr(), tail_neg.c_ptr()); + r = mk(r->get_head(), tail.size(), tail.c_ptr(), tail_neg.c_ptr(), r->name()); TRACE("dl", r->display(m_ctx, tout << "reduced rule\n");); } } From ffa495736243cacd3d3e57af8e609864973285f9 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:39:16 -0400 Subject: [PATCH 06/17] do not use array_der when simplifying rules --- src/muz/base/dl_rule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/base/dl_rule.cpp b/src/muz/base/dl_rule.cpp index af48d0f8b..26a5b748e 100644 --- a/src/muz/base/dl_rule.cpp +++ b/src/muz/base/dl_rule.cpp @@ -54,7 +54,7 @@ namespace datalog { m_head(m), m_args(m), m_hnf(m), - m_qe(m, params_ref()), + m_qe(m, params_ref(), false), m_rwr(m), m_ufproc(m) {} From 1530a39a9648e0380532f8909cba1ad70c58d34e Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:42:44 -0400 Subject: [PATCH 07/17] stubs for spacer-specific API --- src/muz/base/dl_engine_base.h | 16 ++++++++++++++++ src/muz/base/dl_util.h | 1 + 2 files changed, 17 insertions(+) diff --git a/src/muz/base/dl_engine_base.h b/src/muz/base/dl_engine_base.h index 380ae6e07..9c20c712d 100644 --- a/src/muz/base/dl_engine_base.h +++ b/src/muz/base/dl_engine_base.h @@ -20,6 +20,7 @@ Revision History: #define DL_ENGINE_BASE_H_ #include "model/model.h" +#include "muz/base/dl_util.h" namespace datalog { enum DL_ENGINE { @@ -44,6 +45,9 @@ namespace datalog { virtual ~engine_base() {} virtual expr_ref get_answer() = 0; + virtual expr_ref get_ground_sat_answer () { + throw default_exception(std::string("operation is not supported for ") + m_name); + } virtual lbool query(expr* q) = 0; virtual lbool query(unsigned num_rels, func_decl*const* rels) { if (num_rels != 1) return l_undef; @@ -65,6 +69,9 @@ namespace datalog { } return query(q); } + virtual lbool query_from_lvl (expr* q, unsigned lvl) { + throw default_exception(std::string("operation is not supported for ") + m_name); + } virtual void reset_statistics() {} virtual void display_profile(std::ostream& out) {} @@ -72,18 +79,27 @@ namespace datalog { virtual unsigned get_num_levels(func_decl* pred) { throw default_exception(std::string("get_num_levels is not supported for ") + m_name); } + virtual expr_ref get_reachable(func_decl* pred) { + throw default_exception(std::string("operation is not supported for ") + m_name); + } virtual expr_ref get_cover_delta(int level, func_decl* pred) { throw default_exception(std::string("operation is not supported for ") + m_name); } virtual void add_cover(int level, func_decl* pred, expr* property) { throw default_exception(std::string("operation is not supported for ") + m_name); } + virtual void add_invariant (func_decl *pred, expr *property) { + throw default_exception(std::string("operation is not supported for ") + m_name); + } virtual void display_certificate(std::ostream& out) const { throw default_exception(std::string("certificates are not supported for ") + m_name); } virtual model_ref get_model() { return model_ref(alloc(model, m)); } + virtual void get_rules_along_trace (rule_ref_vector& rules) { + throw default_exception(std::string("get_rules_along_trace is not supported for ") + m_name); + } virtual proof_ref get_proof() { return proof_ref(m.mk_asserted(m.mk_true()), m); } diff --git a/src/muz/base/dl_util.h b/src/muz/base/dl_util.h index 48d420bfb..d04e4037c 100644 --- a/src/muz/base/dl_util.h +++ b/src/muz/base/dl_util.h @@ -52,6 +52,7 @@ namespace datalog { ~verbose_action(); }; + typedef ref_vector rule_ref_vector; enum PDR_CACHE_MODE { NO_CACHE, HASH_CACHE, From c3d433ede0deacc278e53eeaa7de4f5fc593f3d7 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:46:59 -0400 Subject: [PATCH 08/17] implemented spacer-specic muz API --- src/muz/base/dl_context.cpp | 77 +++++++++++++++++++++++++++++++++++++ src/muz/base/dl_context.h | 25 ++++++++++++ 2 files changed, 102 insertions(+) diff --git a/src/muz/base/dl_context.cpp b/src/muz/base/dl_context.cpp index 28a456b22..7be434477 100644 --- a/src/muz/base/dl_context.cpp +++ b/src/muz/base/dl_context.cpp @@ -229,6 +229,7 @@ namespace datalog { m_enable_bind_variables(true), m_last_status(OK), m_last_answer(m), + m_last_ground_answer(m), m_engine_type(LAST_ENGINE) { re.set_context(this); updt_params(pa); @@ -306,6 +307,8 @@ namespace datalog { bool context::compress_unbound() const { return m_params->xform_compress_unbound(); } bool context::quantify_arrays() const { return m_params->xform_quantify_arrays(); } bool context::instantiate_quantifiers() const { return m_params->xform_instantiate_quantifiers(); } + bool context::array_blast() const { return m_params->xform_array_blast(); } + bool context::array_blast_full() const { return m_params->xform_array_blast_full(); } void context::register_finite_sort(sort * s, sort_kind k) { @@ -546,10 +549,20 @@ namespace datalog { return m_engine->get_cover_delta(level, pred); } + expr_ref context::get_reachable(func_decl *pred) { + ensure_engine(); + return m_engine->get_reachable(pred); + } void context::add_cover(int level, func_decl* pred, expr* property) { ensure_engine(); m_engine->add_cover(level, pred, property); } + + void context::add_invariant(func_decl* pred, expr *property) + { + ensure_engine(); + m_engine->add_invariant(pred, property); + } void context::check_rules(rule_set& r) { m_rule_properties.set_generate_proof(generate_proof_trace()); @@ -561,6 +574,7 @@ namespace datalog { 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(); @@ -792,6 +806,9 @@ namespace datalog { 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; } @@ -844,8 +861,10 @@ namespace datalog { m_mc = mk_skip_model_converter(); m_last_status = OK; m_last_answer = 0; + m_last_ground_answer = 0; switch (get_engine()) { case DATALOG_ENGINE: + case SPACER_ENGINE: case PDR_ENGINE: case QPDR_ENGINE: case BMC_ENGINE: @@ -867,6 +886,28 @@ namespace datalog { return m_engine->query(query); } + lbool context::query_from_lvl (expr* query, unsigned lvl) { + m_mc = mk_skip_model_converter(); + m_last_status = OK; + m_last_answer = 0; + m_last_ground_answer = 0; + switch (get_engine()) { + case DATALOG_ENGINE: + case SPACER_ENGINE: + case PDR_ENGINE: + case QPDR_ENGINE: + case BMC_ENGINE: + case QBMC_ENGINE: + case TAB_ENGINE: + case CLP_ENGINE: + flush_add_rules(); + break; + default: + UNREACHABLE(); + } + ensure_engine(); + return m_engine->query_from_lvl (query, lvl); + } model_ref context::get_model() { ensure_engine(); return m_engine->get_model(); @@ -905,6 +946,42 @@ namespace datalog { return m_last_answer.get(); } + expr* context::get_ground_sat_answer () { + if (m_last_ground_answer) { + return m_last_ground_answer; + } + ensure_engine (); + m_last_ground_answer = m_engine->get_ground_sat_answer (); + return m_last_ground_answer; + } + + void context::get_rules_along_trace (rule_ref_vector& rules) { + ensure_engine (); + m_engine->get_rules_along_trace (rules); + } + + void context::get_rules_along_trace_as_formulas (expr_ref_vector& rules, svector& names) { + rule_manager& rm = get_rule_manager (); + rule_ref_vector rv (rm); + get_rules_along_trace (rv); + expr_ref fml (m); + rule_ref_vector::iterator it = rv.begin (), end = rv.end (); + for (; it != end; it++) { + m_rule_manager.to_formula (**it, fml); + rules.push_back (fml); + // The concatenated names are already stored last-first, so do not need to be reversed here + const symbol& rule_name = (*it)->name(); + names.push_back (rule_name); + + TRACE ("dl", + if (rule_name == symbol::null) { + tout << "Encountered unnamed rule: "; + (*it)->display(*this, tout); + tout << "\n"; + }); + } + } + void context::display_certificate(std::ostream& out) { ensure_engine(); m_engine->display_certificate(out); diff --git a/src/muz/base/dl_context.h b/src/muz/base/dl_context.h index 0e1d47609..738c2559e 100644 --- a/src/muz/base/dl_context.h +++ b/src/muz/base/dl_context.h @@ -207,6 +207,7 @@ namespace datalog { bool m_enable_bind_variables; execution_result m_last_status; expr_ref m_last_answer; + expr_ref m_last_ground_answer; DL_ENGINE m_engine_type; @@ -277,6 +278,8 @@ namespace datalog { bool xform_bit_blast() const; bool xform_slice() const; bool xform_coi() const; + bool array_blast() const; + bool array_blast_full() const; void register_finite_sort(sort * s, sort_kind k); @@ -407,6 +410,10 @@ namespace datalog { */ unsigned get_num_levels(func_decl* pred); + /** + Retrieve reachable facts of 'pred'. + */ + expr_ref get_reachable(func_decl *pred); /** Retrieve the current cover of 'pred' up to 'level' unfoldings. Return just the delta that is known at 'level'. To @@ -421,6 +428,11 @@ namespace datalog { */ void add_cover(int level, func_decl* pred, expr* property); + /** + Add an invariant of predicate 'pred'. + */ + void add_invariant (func_decl *pred, expr *property); + /** \brief Check rule subsumption. */ @@ -509,6 +521,7 @@ namespace datalog { lbool query(expr* q); + lbool query_from_lvl (expr* q, unsigned lvl); /** \brief retrieve model from inductive invariant that shows query is unsat. @@ -545,6 +558,18 @@ namespace datalog { in the query that are derivable. */ expr* get_answer_as_formula(); + /** + * 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* get_ground_sat_answer (); + + /** + * \brief obtain the sequence of rules along the counterexample trace + */ + void get_rules_along_trace (rule_ref_vector& rules); + + void get_rules_along_trace_as_formulas (expr_ref_vector& rules, svector& names); void collect_statistics(statistics& st) const; From d080c146a2152ced1bbd9d164e4f6a57f8d0dea9 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:50:17 -0400 Subject: [PATCH 09/17] public API for spacer --- src/CMakeLists.txt | 2 + src/api/CMakeLists.txt | 1 + src/api/api_datalog.cpp | 1 + src/api/api_datalog_spacer.inc | 113 +++++++++++++++++++++ src/api/api_qe.cpp | 179 +++++++++++++++++++++++++++++++++ src/api/z3.h | 1 + src/api/z3_spacer.h | 143 ++++++++++++++++++++++++++ 7 files changed, 440 insertions(+) create mode 100644 src/api/api_datalog_spacer.inc create mode 100644 src/api/api_qe.cpp create mode 100644 src/api/z3_spacer.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe9fa2a82..3df33aac9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ set(Z3_API_HEADER_FILES_TO_SCAN z3_optimization.h z3_interp.h z3_fpa.h + z3_spacer.h ) set(Z3_FULL_PATH_API_HEADER_FILES_TO_SCAN "") foreach (header_file ${Z3_API_HEADER_FILES_TO_SCAN}) @@ -169,6 +170,7 @@ set (libz3_public_headers z3_polynomial.h z3_rcf.h z3_v1.h + z3_spacer.h ) foreach (header ${libz3_public_headers}) set_property(TARGET libz3 APPEND PROPERTY diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 79c5fc1c9..a413376ac 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -57,6 +57,7 @@ z3_add_component(api api_parsers.cpp api_pb.cpp api_polynomial.cpp + api_qe.cpp api_quant.cpp api_rcf.cpp api_seq.cpp diff --git a/src/api/api_datalog.cpp b/src/api/api_datalog.cpp index b57247fb1..8f863ed42 100644 --- a/src/api/api_datalog.cpp +++ b/src/api/api_datalog.cpp @@ -605,5 +605,6 @@ extern "C" { } +#include "api_datalog_spacer.inc" }; diff --git a/src/api/api_datalog_spacer.inc b/src/api/api_datalog_spacer.inc new file mode 100644 index 000000000..871d2be63 --- /dev/null +++ b/src/api/api_datalog_spacer.inc @@ -0,0 +1,113 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + api_datalog_spacer.inc + +Abstract: + + Spacer-specific datalog API + +Author: + + Arie Gurfinkel (arie) + +Notes: + this file is included at the bottom of api_datalog.cpp + +--*/ + Z3_lbool Z3_API Z3_fixedpoint_query_from_lvl (Z3_context c, Z3_fixedpoint d, Z3_ast q, unsigned lvl) { + Z3_TRY; + LOG_Z3_fixedpoint_query_from_lvl (c, d, q, lvl); + RESET_ERROR_CODE(); + lbool r = l_undef; + unsigned timeout = to_fixedpoint(d)->m_params.get_uint("timeout", mk_c(c)->get_timeout()); + unsigned rlimit = to_fixedpoint(d)->m_params.get_uint("rlimit", mk_c(c)->get_rlimit()); + { + scoped_rlimit _rlimit(mk_c(c)->m().limit(), rlimit); + cancel_eh eh(mk_c(c)->m().limit()); + api::context::set_interruptable si(*(mk_c(c)), eh); + scoped_timer timer(timeout, &eh); + try { + r = to_fixedpoint_ref(d)->ctx().query_from_lvl (to_expr(q), lvl); + } + catch (z3_exception& ex) { + mk_c(c)->handle_exception(ex); + r = l_undef; + } + to_fixedpoint_ref(d)->ctx().cleanup(); + } + return of_lbool(r); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_ast Z3_API Z3_fixedpoint_get_ground_sat_answer(Z3_context c, Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_get_ground_sat_answer(c, d); + RESET_ERROR_CODE(); + expr* e = to_fixedpoint_ref(d)->ctx().get_ground_sat_answer(); + mk_c(c)->save_ast_trail(e); + RETURN_Z3(of_expr(e)); + Z3_CATCH_RETURN(0); + } + + Z3_ast_vector Z3_API Z3_fixedpoint_get_rules_along_trace( + Z3_context c, + Z3_fixedpoint d) + { + Z3_TRY; + LOG_Z3_fixedpoint_get_rules_along_trace(c, d); + ast_manager& m = mk_c(c)->m(); + Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, *mk_c(c), m); + mk_c(c)->save_object(v); + expr_ref_vector rules(m); + svector names; + + to_fixedpoint_ref(d)->ctx().get_rules_along_trace_as_formulas(rules, names); + for (unsigned i = 0; i < rules.size(); ++i) { + v->m_ast_vector.push_back(rules[i].get()); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(0); + } + + Z3_symbol Z3_API Z3_fixedpoint_get_rule_names_along_trace( + Z3_context c, + Z3_fixedpoint d) + { + Z3_TRY; + LOG_Z3_fixedpoint_get_rule_names_along_trace(c, d); + ast_manager& m = mk_c(c)->m(); + Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, *mk_c(c), m); + mk_c(c)->save_object(v); + expr_ref_vector rules(m); + svector names; + std::stringstream ss; + + to_fixedpoint_ref(d)->ctx().get_rules_along_trace_as_formulas(rules, names); + for (unsigned i = 0; i < names.size(); ++i) { + ss << ";" << names[i].str(); + } + RETURN_Z3(of_symbol(symbol(ss.str().substr(1).c_str()))); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_fixedpoint_add_invariant(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred, Z3_ast property) { + Z3_TRY; + LOG_Z3_fixedpoint_add_invariant(c, d, pred, property); + RESET_ERROR_CODE(); + to_fixedpoint_ref(d)->ctx ().add_invariant(to_func_decl(pred), to_expr(property)); + Z3_CATCH; + } + + Z3_ast Z3_API Z3_fixedpoint_get_reachable(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred) { + Z3_TRY; + LOG_Z3_fixedpoint_get_reachable(c, d, pred); + RESET_ERROR_CODE(); + expr_ref r = to_fixedpoint_ref(d)->ctx().get_reachable(to_func_decl(pred)); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r.get())); + Z3_CATCH_RETURN(0); + } + diff --git a/src/api/api_qe.cpp b/src/api/api_qe.cpp new file mode 100644 index 000000000..ee49acc2a --- /dev/null +++ b/src/api/api_qe.cpp @@ -0,0 +1,179 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + api_qe.cpp + +Abstract: + + Model-based Projection (MBP) and Quantifier Elimination (QE) API + +Author: + + Arie Gurfinkel (arie) + +Notes: + +--*/ + +#include +#include "z3.h" +#include "api_log_macros.h" +#include "api_context.h" +#include "api_util.h" +#include "api_model.h" +#include "api_ast_map.h" +#include "api_ast_vector.h" + +#include "qe_vartest.h" +#include "qe_lite.h" +#include "spacer_util.h" + +#include "expr_map.h" + +extern "C" +{ + Z3_ast Z3_API Z3_qe_model_project (Z3_context c, + Z3_model m, + unsigned num_bounds, + Z3_app const bound[], + Z3_ast body) + { + Z3_TRY; + LOG_Z3_qe_model_project (c, m, num_bounds, bound, body); + RESET_ERROR_CODE(); + + app_ref_vector vars(mk_c(c)->m ()); + for (unsigned i = 0; i < num_bounds; ++i) + { + app *a = to_app (bound [i]); + if (a->get_kind () != AST_APP) + { + SET_ERROR_CODE (Z3_INVALID_ARG); + RETURN_Z3(0); + } + vars.push_back (a); + } + + 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); + mk_c(c)->save_ast_trail (result.get ()); + + return of_expr (result.get ()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_qe_model_project_skolem (Z3_context c, + Z3_model m, + unsigned num_bounds, + Z3_app const bound[], + Z3_ast body, + Z3_ast_map map) + { + Z3_TRY; + LOG_Z3_qe_model_project_skolem (c, m, num_bounds, bound, body, map); + RESET_ERROR_CODE(); + + ast_manager& man = mk_c(c)->m (); + app_ref_vector vars(man); + for (unsigned i = 0; i < num_bounds; ++i) + { + app *a = to_app (bound [i]); + if (a->get_kind () != AST_APP) + { + SET_ERROR_CODE (Z3_INVALID_ARG); + RETURN_Z3(0); + } + vars.push_back (a); + } + + expr_ref result (mk_c(c)->m ()); + result = to_expr (body); + model_ref model (to_model_ref (m)); + expr_map emap (man); + + spacer::qe_project (mk_c(c)->m (), vars, result, model, emap); + mk_c(c)->save_ast_trail (result.get ()); + + obj_map &map_z3 = to_ast_map_ref(map); + + for (expr_map::iterator it = emap.begin(), end = emap.end(); it != end; ++it){ + man.inc_ref(&(it->get_key())); + man.inc_ref(it->get_value()); + map_z3.insert(&(it->get_key()), it->get_value()); + } + + return of_expr (result.get ()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_model_extrapolate (Z3_context c, + Z3_model m, + Z3_ast fml) + { + Z3_TRY; + LOG_Z3_model_extrapolate (c, m, fml); + RESET_ERROR_CODE(); + + model_ref model (to_model_ref (m)); + expr_ref_vector facts (mk_c(c)->m ()); + 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); + + expr_ref result (mk_c(c)->m ()); + result = mk_and (lits); + mk_c(c)->save_ast_trail (result.get ()); + + return of_expr (result.get ()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_qe_lite (Z3_context c, Z3_ast_vector vars, Z3_ast body) + { + Z3_TRY; + LOG_Z3_qe_lite (c, vars, body); + RESET_ERROR_CODE(); + ast_ref_vector &vVars = to_ast_vector_ref (vars); + + app_ref_vector vApps (mk_c(c)->m()); + for (unsigned i = 0; i < vVars.size (); ++i) + { + app *a = to_app (vVars.get (i)); + if (a->get_kind () != AST_APP) + { + SET_ERROR_CODE (Z3_INVALID_ARG); + RETURN_Z3(0); + } + vApps.push_back (a); + } + + expr_ref result (mk_c(c)->m ()); + result = to_expr (body); + + params_ref p; + qe_lite qe (mk_c(c)->m (), p); + qe (vApps, result); + + // -- copy back variables that were not eliminated + if (vApps.size () < vVars.size ()) + { + vVars.reset (); + for (unsigned i = 0; i < vApps.size (); ++i) + vVars.push_back (vApps.get (i)); + } + + mk_c(c)->save_ast_trail (result.get ()); + return of_expr (result); + Z3_CATCH_RETURN(0); + } + +} diff --git a/src/api/z3.h b/src/api/z3.h index 22a1b9b5d..3dd3d11cb 100644 --- a/src/api/z3.h +++ b/src/api/z3.h @@ -33,5 +33,6 @@ Notes: #include "api/z3_interp.h" #include "api/z3_fpa.h" +#include"z3_spacer.h" #endif diff --git a/src/api/z3_spacer.h b/src/api/z3_spacer.h new file mode 100644 index 000000000..c01ee4a4d --- /dev/null +++ b/src/api/z3_spacer.h @@ -0,0 +1,143 @@ +/*++ +Copyright (c) 2017 Arie Gurfinkel + +Module Name: + + z3_spacer.h + +Abstract: + + Spacer API + +Author: + + Arie Gurfinkel (arie) + +Notes: + +--*/ +#ifndef Z3_SPACER_H_ +#define Z3_SPACER_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + + /** \defgroup capi C API */ + /*@{*/ + + /** @name Spacer facilities */ + /*@{*/ + /** + \brief Pose a query against the asserted rules at the given level. + + \code + query ::= (exists (bound-vars) query) + | literals + \endcode + + query returns + - Z3_L_FALSE if the query is unsatisfiable. + - Z3_L_TRUE if the query is satisfiable. Obtain the answer by calling #Z3_fixedpoint_get_answer. + - Z3_L_UNDEF if the query was interrupted, timed out or otherwise failed. + + def_API('Z3_fixedpoint_query_from_lvl', INT, (_in(CONTEXT), _in(FIXEDPOINT), _in(AST), _in(UINT))) + */ + Z3_lbool Z3_API Z3_fixedpoint_query_from_lvl (Z3_context c,Z3_fixedpoint d, Z3_ast query, unsigned lvl); + + /** + \brief Retrieve a bottom-up (from query) sequence of ground facts + + The previous call to Z3_fixedpoint_query must have returned Z3_L_TRUE. + + def_API('Z3_fixedpoint_get_ground_sat_answer', AST, (_in(CONTEXT), _in(FIXEDPOINT))) + */ + Z3_ast Z3_API Z3_fixedpoint_get_ground_sat_answer(Z3_context c,Z3_fixedpoint d); + + /** + \brief Obtain the list of rules along the counterexample trace. + + def_API('Z3_fixedpoint_get_rules_along_trace', AST_VECTOR, (_in(CONTEXT), _in(FIXEDPOINT))) + */ + Z3_ast_vector Z3_API Z3_fixedpoint_get_rules_along_trace(Z3_context c,Z3_fixedpoint d); + + /** + \brief Obtain the list of rules along the counterexample trace. + + def_API('Z3_fixedpoint_get_rule_names_along_trace', SYMBOL, (_in(CONTEXT), _in(FIXEDPOINT))) + */ + Z3_symbol Z3_API Z3_fixedpoint_get_rule_names_along_trace(Z3_context c,Z3_fixedpoint d); + + /** + \brief Add an invariant for the predicate \c pred. + Add an assumed invariant of predicate \c pred. + + Note: this functionality is Spacer specific. + + def_API('Z3_fixedpoint_add_invariant', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(FUNC_DECL), _in(AST))) + */ + void Z3_API Z3_fixedpoint_add_invariant(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred, Z3_ast property); + + + /** + Retrieve reachable states of a predicate. + Note: this functionality is Spacer specific. + + def_API('Z3_fixedpoint_get_reachable', AST, (_in(CONTEXT), _in(FIXEDPOINT), _in(FUNC_DECL))) + */ + Z3_ast Z3_API Z3_fixedpoint_get_reachable(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred); + + /** + \brief Project variables given a model + + def_API('Z3_qe_model_project', AST, (_in(CONTEXT), _in(MODEL), _in(UINT), _in_array(2, APP), _in(AST))) + */ + Z3_ast Z3_API Z3_qe_model_project + (Z3_context c, + Z3_model m, + unsigned num_bounds, + Z3_app const bound[], + Z3_ast body); + + + /** + \brief Project variables given a model + + def_API('Z3_qe_model_project_skolem', AST, (_in(CONTEXT), _in(MODEL), _in(UINT), _in_array(2, APP), _in(AST), _in(AST_MAP))) + */ + Z3_ast Z3_API Z3_qe_model_project_skolem + (Z3_context c, + Z3_model m, + unsigned num_bounds, + Z3_app const bound[], + Z3_ast body, + Z3_ast_map map); + + /** + \brief Extrapolates a model of a formula + + def_API('Z3_model_extrapolate', AST, (_in(CONTEXT), _in(MODEL), _in(AST))) + */ + Z3_ast Z3_API Z3_model_extrapolate + (Z3_context c, + Z3_model m, + Z3_ast fml); + + /** + \brief Best-effort quantifier elimination + + def_API ('Z3_qe_lite', AST, (_in(CONTEXT), _in(AST_VECTOR), _in(AST))) + */ + Z3_ast Z3_qe_lite + (Z3_context c, + Z3_ast_vector vars, + Z3_ast body); + + /*@}*/ + /*@}*/ + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif From 86db446afa1514ed63682cf9b5dcfe7222a4cb4d Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:52:31 -0400 Subject: [PATCH 10/17] python spacer-specific API --- src/api/python/z3/z3.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/api/python/z3/z3.py b/src/api/python/z3/z3.py index a16c1b92b..1452a037e 100644 --- a/src/api/python/z3/z3.py +++ b/src/api/python/z3/z3.py @@ -6536,6 +6536,22 @@ class Fixedpoint(Z3PPObject): r = Z3_fixedpoint_query(self.ctx.ref(), self.fixedpoint, query.as_ast()) return CheckSatResult(r) + def query_from_lvl (self, lvl, *query): + """Query the fixedpoint engine whether formula is derivable starting at the given query level. + """ + query = _get_args(query) + sz = len(query) + if sz >= 1 and isinstance(query[0], FuncDecl): + _z3_assert (False, "unsupported") + else: + if sz == 1: + query = query[0] + else: + query = And(query) + query = self.abstract(query, False) + r = Z3_fixedpoint_query_from_lvl (self.ctx.ref(), self.fixedpoint, query.as_ast(), lvl) + return CheckSatResult(r) + def push(self): """create a backtracking point for added rules, facts and assertions""" Z3_fixedpoint_push(self.ctx.ref(), self.fixedpoint) @@ -6558,6 +6574,23 @@ class Fixedpoint(Z3PPObject): r = Z3_fixedpoint_get_answer(self.ctx.ref(), self.fixedpoint) return _to_expr_ref(r, self.ctx) + def get_ground_sat_answer(self): + """Retrieve a ground cex from last query call.""" + r = Z3_fixedpoint_get_ground_sat_answer(self.ctx.ref(), self.fixedpoint) + return _to_expr_ref(r, self.ctx) + + def get_rules_along_trace(self): + """retrieve rules along the counterexample trace""" + return AstVector(Z3_fixedpoint_get_rules_along_trace(self.ctx.ref(), self.fixedpoint), self.ctx) + + def get_rule_names_along_trace(self): + """retrieve rule names along the counterexample trace""" + # this is a hack as I don't know how to return a list of symbols from C++; + # obtain names as a single string separated by semicolons + names = _symbol2py (self.ctx, Z3_fixedpoint_get_rule_names_along_trace(self.ctx.ref(), self.fixedpoint)) + # split into individual names + return names.split (';') + def get_num_levels(self, predicate): """Retrieve number of levels used for predicate in PDR engine""" return Z3_fixedpoint_get_num_levels(self.ctx.ref(), self.fixedpoint, predicate.ast) From 33c81524d2290eda1b55a1a3dfb7af2a28bb03a3 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:57:29 -0400 Subject: [PATCH 11/17] optionally disable propagate variable equivalences in interp_tail_simplifier --- src/muz/transforms/dl_mk_interp_tail_simplifier.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp b/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp index 64a2a2aca..1559f4f65 100644 --- a/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp +++ b/src/muz/transforms/dl_mk_interp_tail_simplifier.cpp @@ -27,6 +27,7 @@ Revision History: #include "muz/transforms/dl_mk_interp_tail_simplifier.h" #include "ast/ast_util.h" +#include "fixedpoint_params.hpp" namespace datalog { // ----------------------------------- @@ -397,6 +398,8 @@ namespace datalog { } bool mk_interp_tail_simplifier::propagate_variable_equivalences(rule * r, rule_ref& res) { + if (!m_context.get_params ().xform_tail_simplifier_pve ()) + return false; unsigned u_len = r->get_uninterpreted_tail_size(); unsigned len = r->get_tail_size(); if (u_len == len) { From f5fa6b0bcb80b2ee16930ec1df6445b230d8c6e0 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 15:58:00 -0400 Subject: [PATCH 12/17] optionally disable subsumption checker --- src/muz/transforms/dl_mk_subsumption_checker.cpp | 3 +++ src/muz/transforms/dl_transforms.cpp | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/muz/transforms/dl_mk_subsumption_checker.cpp b/src/muz/transforms/dl_mk_subsumption_checker.cpp index 6a3ea8735..d3b959792 100644 --- a/src/muz/transforms/dl_mk_subsumption_checker.cpp +++ b/src/muz/transforms/dl_mk_subsumption_checker.cpp @@ -25,6 +25,7 @@ Revision History: #include "ast/rewriter/rewriter_def.h" #include "muz/transforms/dl_mk_subsumption_checker.h" +#include "fixedpoint_params.hpp" namespace datalog { @@ -328,6 +329,8 @@ namespace datalog { rule_set * mk_subsumption_checker::operator()(rule_set const & source) { // TODO mc + if (!m_context.get_params ().xform_subsumption_checker()) + return 0; m_have_new_total_rule = false; collect_ground_unconditional_rule_heads(source); diff --git a/src/muz/transforms/dl_transforms.cpp b/src/muz/transforms/dl_transforms.cpp index 48e956979..c727a29d4 100644 --- a/src/muz/transforms/dl_transforms.cpp +++ b/src/muz/transforms/dl_transforms.cpp @@ -51,17 +51,22 @@ namespace datalog { } transf.register_plugin(alloc(datalog::mk_quantifier_instantiation, ctx, 37000)); + if (ctx.get_params().datalog_subsumption()) { transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 35005)); + } transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 35000)); transf.register_plugin(alloc(datalog::mk_coi_filter, ctx, 34990)); transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx, 34980)); //and another round of inlining + if (ctx.get_params().datalog_subsumption()) { transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34975)); + } 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)); transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34920)); @@ -69,6 +74,10 @@ namespace datalog { transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34900)); transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34890)); transf.register_plugin(alloc(datalog::mk_subsumption_checker, ctx, 34880)); + } + else { + transf.register_plugin(alloc(datalog::mk_rule_inliner, ctx, 34930)); + } transf.register_plugin(alloc(datalog::mk_bit_blast, ctx, 35000)); transf.register_plugin(alloc(datalog::mk_karr_invariants, ctx, 36010)); From 2c7a39d580194d2f63cfea4b828be22c0c73ec7a Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 16:00:36 -0400 Subject: [PATCH 13/17] Optionally blast arrays This changes the default behavior of always blasting arrays. The old behavior can be restored using fixedpoint.xform.array_blast=true --- src/muz/transforms/dl_mk_array_blast.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/muz/transforms/dl_mk_array_blast.cpp b/src/muz/transforms/dl_mk_array_blast.cpp index 373edcd4b..8fe3f0e43 100644 --- a/src/muz/transforms/dl_mk_array_blast.cpp +++ b/src/muz/transforms/dl_mk_array_blast.cpp @@ -319,6 +319,9 @@ namespace datalog { rule_set * mk_array_blast::operator()(rule_set const & source) { + if (!m_ctx.array_blast ()) { + return 0; + } rule_set* rules = alloc(rule_set, m_ctx); rules->inherit_predicates(source); rule_set::iterator it = source.begin(), end = source.end(); From 7168451201b4989ba6a426ec3e2754c43752748a Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 16:05:06 -0400 Subject: [PATCH 14/17] eager quantifier instantiation for quantified array properties --- src/muz/transforms/CMakeLists.txt | 2 + src/muz/transforms/dl_mk_array_eq_rewrite.cpp | 140 ++++++++ src/muz/transforms/dl_mk_array_eq_rewrite.h | 54 +++ .../transforms/dl_mk_array_instantiation.cpp | 324 ++++++++++++++++++ .../transforms/dl_mk_array_instantiation.h | 123 +++++++ src/muz/transforms/dl_transforms.cpp | 10 +- 6 files changed, 651 insertions(+), 2 deletions(-) create mode 100644 src/muz/transforms/dl_mk_array_eq_rewrite.cpp create mode 100644 src/muz/transforms/dl_mk_array_eq_rewrite.h create mode 100644 src/muz/transforms/dl_mk_array_instantiation.cpp create mode 100644 src/muz/transforms/dl_mk_array_instantiation.h diff --git a/src/muz/transforms/CMakeLists.txt b/src/muz/transforms/CMakeLists.txt index 6a0a1ac9c..5e8829d42 100644 --- a/src/muz/transforms/CMakeLists.txt +++ b/src/muz/transforms/CMakeLists.txt @@ -21,6 +21,8 @@ z3_add_component(transforms dl_mk_unbound_compressor.cpp dl_mk_unfold.cpp dl_transforms.cpp + dl_mk_array_eq_rewrite.cpp + dl_mk_array_instantiation.cpp COMPONENT_DEPENDENCIES dataflow hilbert diff --git a/src/muz/transforms/dl_mk_array_eq_rewrite.cpp b/src/muz/transforms/dl_mk_array_eq_rewrite.cpp new file mode 100644 index 000000000..c9caa6148 --- /dev/null +++ b/src/muz/transforms/dl_mk_array_eq_rewrite.cpp @@ -0,0 +1,140 @@ +/*++ + +Module Name: + + dl_mk_array_eq_rewrite.h + +Abstract: + Selects a representative for array equivalence classes. + +Author: + + Julien Braine + +Revision History: + +--*/ + + +#include "dl_mk_array_eq_rewrite.h" +#include "dl_context.h" +#include "pattern_inference.h" +#include "dl_context.h" +#include "expr_safe_replace.h" +#include "expr_abstract.h" +#include"fixedpoint_params.hpp" +#include "../spacer/obj_equiv_class.h" + +namespace datalog { + + mk_array_eq_rewrite::mk_array_eq_rewrite( + context & ctx, unsigned priority): + plugin(priority), + m(ctx.get_manager()), + m_ctx(ctx), + m_a(m) + { + } + + rule_set * mk_array_eq_rewrite::operator()(rule_set const & source) + { + src_set = &source; + rule_set * result = alloc(rule_set, m_ctx); + result->inherit_predicates(source); + dst=result; + unsigned nbrules = source.get_num_rules(); + src_manager = &source.get_rule_manager(); + for(unsigned i =0;iget_counter().get_max_rule_var(r)+1; + + + expr_ref_vector new_tail(m); + unsigned nb_predicates = r.get_uninterpreted_tail_size(); + unsigned tail_size = r.get_tail_size(); + for(unsigned i=0;imk_rule(m.mk_implies(m.mk_and(res_conjs.size(), res_conjs.c_ptr()), r.get_head()), pr, dest, r.name()); + } + + expr* mk_array_eq_rewrite::replace(expr* e, expr* new_val, expr* old_val) + { + if(e==old_val) + return new_val; + else if(!is_app(e)) + { + return e; + } + app*f = to_app(e); + ptr_vector n_args; + for(unsigned i=0;iget_num_args();i++) + { + n_args.push_back(replace(f->get_arg(i), new_val, old_val)); + } + return m.mk_app(f->get_decl(), n_args.size(), n_args.c_ptr()); + } + +} diff --git a/src/muz/transforms/dl_mk_array_eq_rewrite.h b/src/muz/transforms/dl_mk_array_eq_rewrite.h new file mode 100644 index 000000000..0229ff487 --- /dev/null +++ b/src/muz/transforms/dl_mk_array_eq_rewrite.h @@ -0,0 +1,54 @@ +/*++ + +Module Name: + + dl_mk_array_eq_rewrite.h + +Abstract: + Selects a representative for array equivalence classes. + +Author: + + Julien Braine + +Revision History: + +--*/ + +#ifndef DL_MK_ARRAY_EQ_REWRITE_H_ +#define DL_MK_ARRAY_EQ_REWRITE_H_ + + +#include "dl_rule_transformer.h" +#include "../spacer/obj_equiv_class.h" + +namespace datalog { + + class context; + class mk_array_eq_rewrite : public rule_transformer::plugin { + //Context objects + ast_manager& m; + context& m_ctx; + array_util m_a; + + //Rule set context + const rule_set*src_set; + rule_set*dst; + rule_manager* src_manager; + unsigned cnt;//Index for new variables + + expr* replace(expr* e, expr* new_val, expr* old_val); + void instantiate_rule(const rule& r, rule_set & dest); + + public: + mk_array_eq_rewrite(context & ctx, unsigned priority); + rule_set * operator()(rule_set const & source); + virtual ~mk_array_eq_rewrite(){} + }; + + + +}; + +#endif /* DL_MK_ARRAY_EQ_REWRITE_H_ */ + diff --git a/src/muz/transforms/dl_mk_array_instantiation.cpp b/src/muz/transforms/dl_mk_array_instantiation.cpp new file mode 100644 index 000000000..762884545 --- /dev/null +++ b/src/muz/transforms/dl_mk_array_instantiation.cpp @@ -0,0 +1,324 @@ +/*++ + +Module Name: + + dl_mk_array_instantiation.h + +Abstract: + + Does array instantiation + +Author: + + Julien Braine + +Revision History: + +--*/ + + +#include "dl_mk_array_instantiation.h" +#include "dl_context.h" +#include "pattern_inference.h" +#include "dl_context.h" +#include "expr_safe_replace.h" +#include "expr_abstract.h" +#include"fixedpoint_params.hpp" +#include "../spacer/obj_equiv_class.h" + +namespace datalog { + + mk_array_instantiation::mk_array_instantiation( + context & ctx, unsigned priority): + plugin(priority), + m(ctx.get_manager()), + m_ctx(ctx), + m_a(m), + eq_classes(m), + ownership(m) + { + } + + rule_set * mk_array_instantiation::operator()(rule_set const & source) + { + std::cout<<"Array Instantiation called with parameters :" + <<" enforce="<display(std::cout); + return result; + } + + void mk_array_instantiation::instantiate_rule(const rule& r, rule_set & dest) + { + //Reset everything + selects.reset(); + eq_classes.reset(); + cnt = src_manager->get_counter().get_max_rule_var(r)+1; + done_selects.reset(); + ownership.reset(); + + expr_ref_vector phi(m); + expr_ref_vector preds(m); + expr_ref new_head = create_head(to_app(r.get_head())); + unsigned nb_predicates = r.get_uninterpreted_tail_size(); + unsigned tail_size = r.get_tail_size(); + for(unsigned i=0;i::iterator it = done_selects.begin(); it!=done_selects.end(); ++it) + { + expr_ref tmp(m); + tmp = &it->get_key(); + new_tail.push_back(m.mk_eq(it->get_value(), tmp)); + } + proof_ref pr(m); + src_manager->mk_rule(m.mk_implies(m.mk_and(new_tail.size(), new_tail.c_ptr()), new_head), pr, dest, r.name()); + } + + expr_ref mk_array_instantiation::create_head(app* old_head) + { + expr_ref_vector new_args(m); + for(unsigned i=0;iget_num_args();i++) + { + expr*arg = old_head->get_arg(i); + if(m_a.is_array(get_sort(arg))) + { + for(unsigned k=0; k< m_ctx.get_params().xform_instantiate_arrays_nb_quantifier();k++) + { + expr_ref_vector dummy_args(m); + dummy_args.push_back(arg); + for(unsigned i=0;i()); + selects[arg].push_back(select); + } + if(!m_ctx.get_params().xform_instantiate_arrays_enforce()) + new_args.push_back(arg); + } + else + new_args.push_back(arg); + } + return create_pred(old_head, new_args); + } + + + void mk_array_instantiation::retrieve_selects(expr* e) + { + //If the expression is not a function application, we ignore it + if (!is_app(e)) { + return; + } + app*f=to_app(e); + //Call the function recursively on all arguments + unsigned nbargs = f->get_num_args(); + for(unsigned i=0;iget_arg(i)); + } + //If it is a select, then add it to selects + if(m_a.is_select(f)) + { + SASSERT(!m_a.is_array(get_sort(e))); + selects.insert_if_not_there(f->get_arg(0), ptr_vector()); + selects[f->get_arg(0)].push_back(e); + } + //If it is a condition between arrays, for example the result of a store, then add it to the equiv_classes + if(m_a.is_store(f)) + { + eq_classes.merge(e, f->get_arg(0)); + } + else if(m.is_eq(f) && m_a.is_array(get_sort(f->get_arg(0)))) + { + eq_classes.merge(f->get_arg(0), f->get_arg(1)); + } + } + + + expr_ref_vector mk_array_instantiation::getId(app*old_pred, const expr_ref_vector& n_args) + { + expr_ref_vector res(m); + for(unsigned i=0;iget_num_args();j++) + { + res.push_back(select->get_arg(j)); + } + } + } + return res; + } + + expr_ref mk_array_instantiation::create_pred(app*old_pred, expr_ref_vector& n_args) + { + expr_ref_vector new_args(m); + new_args.append(n_args); + new_args.append(getId(old_pred, n_args)); + for(unsigned i=0;iget_decl()->get_name().str()+"!inst").c_str()), new_sorts.size(), new_sorts.c_ptr(), old_pred->get_decl()->get_range()); + m_ctx.register_predicate(fun_decl, false); + if(src_set->is_output_predicate(old_pred->get_decl())) + dst->set_output_predicate(fun_decl); + res=m.mk_app(fun_decl,new_args.size(), new_args.c_ptr()); + return res; + } + + var * mk_array_instantiation::mk_select_var(expr* select) + { + var*result; + if(!done_selects.find(select, result)) + { + ownership.push_back(select); + result = m.mk_var(cnt, get_sort(select)); + cnt++; + done_selects.insert(select, result); + } + return result; + } + + expr_ref mk_array_instantiation::rewrite_select(expr*array, expr*select) + { + app*s = to_app(select); + expr_ref res(m); + expr_ref_vector args(m); + args.push_back(array); + for(unsigned i=1; iget_num_args();i++) + { + args.push_back(s->get_arg(i)); + } + res = m_a.mk_select(args.size(), args.c_ptr()); + return res; + } + + expr_ref_vector mk_array_instantiation::retrieve_all_selects(expr*array) + { + expr_ref_vector all_selects(m); + for(spacer::expr_equiv_class::iterator it = eq_classes.begin(array); + it != eq_classes.end(array); ++it) + { + selects.insert_if_not_there(*it, ptr_vector()); + ptr_vector& select_ops = selects[*it]; + for(unsigned i=0;iget_num_args(); + //Stores, for each old position, the list of a new possible arguments + vector arg_correspondance; + for(unsigned i=0;iget_arg(i), m); + if(m_a.is_array(get_sort(arg))) + { + vector arg_possibilities(m_ctx.get_params().xform_instantiate_arrays_nb_quantifier(), retrieve_all_selects(arg)); + arg_correspondance.append(arg_possibilities); + if(!m_ctx.get_params().xform_instantiate_arrays_enforce()) + { + expr_ref_vector tmp(m); + tmp.push_back(arg); + arg_correspondance.push_back(tmp); + } + } + else + { + expr_ref_vector tmp(m); + tmp.push_back(arg); + arg_correspondance.push_back(tmp); + } + } + //Now, we need to deal with every combination + + expr_ref_vector res(m); + + svector chosen(arg_correspondance.size(), 0u); + while(1) + { + expr_ref_vector new_args(m); + for(unsigned i=0;i=arg_correspondance[pos].size()); + chosen[pos]++; + } + } +} diff --git a/src/muz/transforms/dl_mk_array_instantiation.h b/src/muz/transforms/dl_mk_array_instantiation.h new file mode 100644 index 000000000..0af57ee84 --- /dev/null +++ b/src/muz/transforms/dl_mk_array_instantiation.h @@ -0,0 +1,123 @@ +/*++ + +Module Name: + + dl_mk_array_instantiation.h + +Abstract: + Transforms predicates so that array invariants can be discovered. + + Motivation : Given a predicate P(a), no quantifier-free solution can express that P(a) <=> forall i, P(a[i]) = 0 + + Solution : Introduce a fresh variable i, and transform P(a) into P!inst(i, a). + Now, (P!inst(i,a) := a[i] = 0) <=> P(a) := forall i, a[i] = 0. + + Transformation on Horn rules: + P(a, args) /\ phi(a, args, args') => P'(args') (for simplicity, assume there are no arrays in args'). + Is transformed into: + (/\_r in read_indices(phi) P!inst(r, a, args)) /\ phi(a, args, args') => P'(args') + + Limitations : This technique can only discover invariants on arrays that depend on one quantifier. + Related work : Techniques relying on adding quantifiers and eliminating them. See dl_mk_quantifier_abstraction and dl_mk_quantifier_instantiation + +Implementation: + The implementation follows the solution suggested above, with more options. The addition of options implies that in the simple + case described above, we in fact have P(a) transformed into P(i, a[i], a). + + 1) Dealing with multiple quantifiers -> The options fixedpoint.xform.instantiate_arrays.nb_quantifier gives the number of quantifiers per array. + + 2) Inforcing the instantiation -> We suggest an option (enforce_instantiation) to enforce this abstraction. This transforms + P(a) into P(i, a[i]). This enforces the solver to limit the space search at the cost of imprecise results. This option + corresponds to fixedpoint.xform.instantiate_arrays.enforce + + 3) Adding slices in the mix -> We wish to have the possibility to further restrict the search space: we want to smash cells, given a smashing rule. + For example, in for loops j=0; j P'(...) is transformed into + (/\_r in read_indices(phi) P!inst(id_r, a[r], a) /\ GetId(r) = id_r) /\ phi(a, ...) => P'(...). + Note : when no slicing is done, GetId(i) = i. + This option corresponds to fixedpoint.xform.instantiate_arrays.slice_technique + + Although we described GetId as returning integers, there is no reason to restrict the type of ids to integers. A more direct method, + for the 0<=i > selects; + spacer::expr_equiv_class eq_classes; + unsigned cnt;//Index for new variables + obj_map done_selects; + expr_ref_vector ownership; + + //Helper functions + void instantiate_rule(const rule& r, rule_set & dest);//Instantiates the rule + void retrieve_selects(expr* e);//Retrieves all selects (fills the selects and eq_classes members) + expr_ref rewrite_select(expr*array, expr*select);//Rewrites select(a, args) to select(array, args) + expr_ref_vector retrieve_all_selects(expr*array);//Retrieves all selects linked to a given array (using eq classes and selects) + expr_ref_vector instantiate_pred(app*old_pred);//Returns all the instantiation of a given predicate + expr_ref create_pred(app*old_pred, expr_ref_vector& new_args);//Creates a predicate + expr_ref create_head(app* old_head);//Creates the new head + var * mk_select_var(expr* select); + + /*Given the old predicate, and the new arguments for the new predicate, returns the new setId arguments. + By default getId(P(x, y, a, b), (x, y, a[i], a[j], a, b[k], b[l], b)) (nb_quantifier=2, enforce=false) + returns (i,j,k,l) + So that the final created predicate is P!inst(x, y, a[i], a[j], a, b[k], b[l], b, i, j, k, l) + */ + expr_ref_vector getId(app*old_pred, const expr_ref_vector& new_args); + public: + mk_array_instantiation(context & ctx, unsigned priority); + rule_set * operator()(rule_set const & source); + virtual ~mk_array_instantiation(){} + }; + + + +}; + +#endif /* DL_MK_ARRAY_INSTANTIATION_H_ */ diff --git a/src/muz/transforms/dl_transforms.cpp b/src/muz/transforms/dl_transforms.cpp index c727a29d4..4569cb572 100644 --- a/src/muz/transforms/dl_transforms.cpp +++ b/src/muz/transforms/dl_transforms.cpp @@ -33,7 +33,8 @@ Revision History: #include "muz/transforms/dl_mk_quantifier_instantiation.h" #include "muz/transforms/dl_mk_subsumption_checker.h" #include "muz/transforms/dl_mk_scale.h" -#include"fixedpoint_params.hpp" +#include "muz/base/fixedpoint_params.hpp" +#include "muz/transforms/dl_mk_array_eq_rewrite.h" namespace datalog { @@ -46,6 +47,11 @@ namespace datalog { transf.register_plugin(alloc(datalog::mk_coi_filter, ctx)); transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, ctx)); + if (ctx.get_params().xform_instantiate_arrays()) { + transf.register_plugin(alloc(datalog::mk_array_instantiation, ctx, 34999)); + } + if(ctx.get_params().xform_transform_arrays()) + transf.register_plugin(alloc(datalog::mk_array_eq_rewrite, ctx, 34998)); if (ctx.get_params().xform_quantify_arrays()) { transf.register_plugin(alloc(datalog::mk_quantifier_abstraction, ctx, 38000)); } @@ -83,7 +89,7 @@ namespace datalog { transf.register_plugin(alloc(datalog::mk_karr_invariants, ctx, 36010)); transf.register_plugin(alloc(datalog::mk_scale, ctx, 36030)); if (!ctx.get_params().xform_quantify_arrays()) { - transf.register_plugin(alloc(datalog::mk_array_blast, ctx, 36000)); + transf.register_plugin(alloc(datalog::mk_array_blast, ctx, 35999)); } if (ctx.get_params().xform_magic()) { transf.register_plugin(alloc(datalog::mk_magic_symbolic, ctx, 36020)); From 97c5ab30d5e54c56172d63062615b2c990e33149 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 16:07:16 -0400 Subject: [PATCH 15/17] small improvements to bmc engine courtesy of Marc Brockschmidt --- src/muz/bmc/dl_bmc_engine.cpp | 20 ++++++++++++++++++-- src/muz/bmc/dl_bmc_engine.h | 2 ++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/muz/bmc/dl_bmc_engine.cpp b/src/muz/bmc/dl_bmc_engine.cpp index 186a021da..e78fb4331 100644 --- a/src/muz/bmc/dl_bmc_engine.cpp +++ b/src/muz/bmc/dl_bmc_engine.cpp @@ -32,6 +32,7 @@ Revision History: #include "muz/transforms/dl_transforms.h" #include "muz/transforms/dl_mk_rule_inliner.h" #include "ast/scoped_proof.h" +#include "muz/base/fixedpoint_params.hpp" namespace datalog { @@ -143,6 +144,7 @@ namespace datalog { b.m_fparams.m_model = true; b.m_fparams.m_model_compact = true; b.m_fparams.m_mbqi = true; + b.m_rule_trace.reset(); } void mk_qrule_vars(datalog::rule const& r, unsigned rule_id, expr_ref_vector& sub) { @@ -279,6 +281,7 @@ namespace datalog { } SASSERT(r); mk_qrule_vars(*r, i, sub); + b.m_rule_trace.push_back(r); // we have rule, we have variable names of rule. // extract values for the variables in the rule. @@ -470,6 +473,7 @@ namespace datalog { b.m_fparams.m_model_compact = true; // b.m_fparams.m_mbqi = true; b.m_fparams.m_relevancy_lvl = 2; + b.m_rule_trace.reset(); } lbool check(unsigned level) { @@ -507,6 +511,7 @@ namespace datalog { } } SASSERT(r); + b.m_rule_trace.push_back(r); rm.to_formula(*r, fml); IF_VERBOSE(1, verbose_stream() << mk_pp(fml, m) << "\n";); prs.push_back(r->get_proof()); @@ -760,6 +765,7 @@ namespace datalog { b.m_fparams.m_model_compact = true; b.m_fparams.m_mbqi = false; b.m_fparams.m_relevancy_lvl = 2; + b.m_rule_trace.reset(); } func_decl_ref mk_predicate(func_decl* pred) { @@ -1078,6 +1084,7 @@ namespace datalog { } head = rl->get_head(); pr = m.mk_hyper_resolve(sz+1, prs.c_ptr(), head, positions, substs); + b.m_rule_trace.push_back(rl.get()); return pr; } } @@ -1154,7 +1161,8 @@ namespace datalog { lbool check() { setup(); - for (unsigned i = 0; ; ++i) { + unsigned max_depth = b.m_ctx.get_params().bmc_linear_unrolling_depth(); + for (unsigned i = 0; i < max_depth; ++i) { IF_VERBOSE(1, verbose_stream() << "level: " << i << "\n";); b.checkpoint(); compile(i); @@ -1167,6 +1175,7 @@ namespace datalog { return res; } } + return l_undef; } private: @@ -1202,6 +1211,7 @@ namespace datalog { } } SASSERT(r); + b.m_rule_trace.push_back(r); mk_rule_vars(*r, level, i, sub); // we have rule, we have variable names of rule. @@ -1284,6 +1294,7 @@ namespace datalog { b.m_fparams.m_model_compact = true; b.m_fparams.m_mbqi = false; // m_fparams.m_auto_config = false; + b.m_rule_trace.reset(); } @@ -1426,7 +1437,8 @@ namespace datalog { m_solver(m, m_fparams), m_rules(ctx), m_query_pred(m), - m_answer(m) { + m_answer(m), + m_rule_trace(ctx.get_rule_manager()) { } bmc::~bmc() {} @@ -1530,6 +1542,10 @@ namespace datalog { return m_answer; } + void bmc::get_rules_along_trace(datalog::rule_ref_vector& rules) { + rules.append(m_rule_trace); + } + void bmc::compile(rule_set const& rules, expr_ref_vector& fmls, unsigned level) { nonlinear nl(*this); nl.compile(rules, fmls, level); diff --git a/src/muz/bmc/dl_bmc_engine.h b/src/muz/bmc/dl_bmc_engine.h index 39bdd1fbe..fd5ce92e6 100644 --- a/src/muz/bmc/dl_bmc_engine.h +++ b/src/muz/bmc/dl_bmc_engine.h @@ -38,6 +38,7 @@ namespace datalog { rule_set m_rules; func_decl_ref m_query_pred; expr_ref m_answer; + rule_ref_vector m_rule_trace; void checkpoint(); @@ -63,6 +64,7 @@ namespace datalog { void collect_statistics(statistics& st) const; void reset_statistics(); + void get_rules_along_trace(datalog::rule_ref_vector& rules); expr_ref get_answer(); From f465a2225aa3223a6c31403f94d33b27f3edb99f Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 17:14:43 -0400 Subject: [PATCH 16/17] fixing include paths --- src/muz/bmc/dl_bmc_engine.cpp | 3 ++- src/muz/transforms/dl_transforms.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/muz/bmc/dl_bmc_engine.cpp b/src/muz/bmc/dl_bmc_engine.cpp index e78fb4331..fc5067355 100644 --- a/src/muz/bmc/dl_bmc_engine.cpp +++ b/src/muz/bmc/dl_bmc_engine.cpp @@ -32,7 +32,8 @@ Revision History: #include "muz/transforms/dl_transforms.h" #include "muz/transforms/dl_mk_rule_inliner.h" #include "ast/scoped_proof.h" -#include "muz/base/fixedpoint_params.hpp" + +#include "fixedpoint_params.hpp" namespace datalog { diff --git a/src/muz/transforms/dl_transforms.cpp b/src/muz/transforms/dl_transforms.cpp index 4569cb572..b684139f6 100644 --- a/src/muz/transforms/dl_transforms.cpp +++ b/src/muz/transforms/dl_transforms.cpp @@ -33,8 +33,9 @@ Revision History: #include "muz/transforms/dl_mk_quantifier_instantiation.h" #include "muz/transforms/dl_mk_subsumption_checker.h" #include "muz/transforms/dl_mk_scale.h" -#include "muz/base/fixedpoint_params.hpp" #include "muz/transforms/dl_mk_array_eq_rewrite.h" +#include "muz/transforms/dl_mk_array_instantiation.h" +#include "fixedpoint_params.hpp" namespace datalog { From ffff16632d18e11d68224fc8d756930237774449 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 31 Jul 2017 17:30:11 -0400 Subject: [PATCH 17/17] updating includes --- src/muz/spacer/obj_equiv_class.h | 4 +- src/muz/spacer/spacer_antiunify.cpp | 14 +++---- src/muz/spacer/spacer_antiunify.h | 2 +- src/muz/spacer/spacer_context.cpp | 53 ++++++++++++------------ src/muz/spacer/spacer_dl_interface.cpp | 34 +++++++-------- src/muz/spacer/spacer_dl_interface.h | 10 ++--- src/muz/spacer/spacer_farkas_learner.cpp | 31 +++++++------- src/muz/spacer/spacer_farkas_learner.h | 2 +- src/muz/spacer/spacer_generalizers.cpp | 12 +++--- src/muz/spacer/spacer_generalizers.h | 4 +- 10 files changed, 82 insertions(+), 84 deletions(-) diff --git a/src/muz/spacer/obj_equiv_class.h b/src/muz/spacer/obj_equiv_class.h index b3184655c..7f58efa7d 100644 --- a/src/muz/spacer/obj_equiv_class.h +++ b/src/muz/spacer/obj_equiv_class.h @@ -25,8 +25,8 @@ Revision History: #ifndef OBJ_EQUIV_CLASS_H_ #define OBJ_EQUIV_CLASS_H_ -#include "union_find.h" -#include "ast_util.h" +#include "util/union_find.h" +#include "ast/ast_util.h" namespace spacer { //All functions naturally add their parameters to the union_find class diff --git a/src/muz/spacer/spacer_antiunify.cpp b/src/muz/spacer/spacer_antiunify.cpp index 56fbbb8f0..6c382a723 100644 --- a/src/muz/spacer/spacer_antiunify.cpp +++ b/src/muz/spacer/spacer_antiunify.cpp @@ -18,13 +18,13 @@ Revision History: --*/ -#include"spacer_antiunify.h" -#include"ast.h" -#include"rewriter.h" -#include"rewriter_def.h" -#include"arith_decl_plugin.h" -#include"ast_util.h" -#include"expr_abstract.h" +#include"muz/spacer/spacer_antiunify.h" +#include"ast/ast.h" +#include"ast/rewriter/rewriter.h" +#include"ast/rewriter/rewriter_def.h" +#include"ast/arith_decl_plugin.h" +#include"ast/ast_util.h" +#include"ast/expr_abstract.h" namespace spacer { diff --git a/src/muz/spacer/spacer_antiunify.h b/src/muz/spacer/spacer_antiunify.h index 690bc4678..2b86f67ec 100644 --- a/src/muz/spacer/spacer_antiunify.h +++ b/src/muz/spacer/spacer_antiunify.h @@ -21,7 +21,7 @@ Revision History: #ifndef _SPACER_ANTIUNIFY_H_ #define _SPACER_ANTIUNIFY_H_ -#include "ast.h" +#include "ast/ast.h" namespace spacer { class anti_unifier diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 40709de7f..a8ee01cff 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -24,34 +24,33 @@ Notes: #include #include -#include "dl_util.h" -#include "rewriter.h" -#include "rewriter_def.h" -#include "var_subst.h" -#include "util.h" -#include "spacer_prop_solver.h" -#include "spacer_context.h" -#include "spacer_generalizers.h" -#include "for_each_expr.h" -#include "dl_rule_set.h" -#include "unit_subsumption_tactic.h" -#include "model_smt2_pp.h" -#include "dl_mk_rule_inliner.h" -#include "ast_smt2_pp.h" -#include "ast_ll_pp.h" -#include "ast_util.h" -#include "proof_checker.h" -#include "smt_value_sort.h" -#include "proof_utils.h" -#include "scoped_proof.h" -#include "spacer_qe_project.h" -#include "blast_term_ite_tactic.h" +#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/spacer/spacer_prop_solver.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_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 "ast/ast_ll_pp.h" +#include "ast/ast_util.h" +#include "ast/proof_checker/proof_checker.h" +#include "smt/smt_value_sort.h" +#include "ast/scoped_proof.h" +#include "muz/spacer/spacer_qe_project.h" +#include "tactic/core/blast_term_ite_tactic.h" -#include "timeit.h" -#include "luby.h" -#include "expr_safe_replace.h" -#include "expr_abstract.h" -#include "obj_equiv_class.h" +#include "util/timeit.h" +#include "util/luby.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "ast/expr_abstract.h" +#include "muz/spacer/obj_equiv_class.h" namespace spacer { diff --git a/src/muz/spacer/spacer_dl_interface.cpp b/src/muz/spacer/spacer_dl_interface.cpp index 264809f72..78776e074 100644 --- a/src/muz/spacer/spacer_dl_interface.cpp +++ b/src/muz/spacer/spacer_dl_interface.cpp @@ -17,23 +17,23 @@ Revision History: --*/ -#include "dl_context.h" -#include "dl_mk_coi_filter.h" -#include "dl_mk_interp_tail_simplifier.h" -#include "dl_mk_subsumption_checker.h" -#include "dl_mk_rule_inliner.h" -#include "dl_rule.h" -#include "dl_rule_transformer.h" -#include "smt2parser.h" -#include "spacer_context.h" -#include "spacer_dl_interface.h" -#include "dl_rule_set.h" -#include "dl_mk_slice.h" -#include "dl_mk_unfold.h" -#include "dl_mk_coalesce.h" -#include "model_smt2_pp.h" -#include "scoped_proof.h" -#include "dl_transforms.h" +#include "muz/base/dl_context.h" +#include "muz/transforms/dl_mk_coi_filter.h" +#include "muz/transforms/dl_mk_interp_tail_simplifier.h" +#include "muz/transforms/dl_mk_subsumption_checker.h" +#include "muz/transforms/dl_mk_rule_inliner.h" +#include "muz/base/dl_rule.h" +#include "muz/base/dl_rule_transformer.h" +#include "parsers/smt2/smt2parser.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_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 "model/model_smt2_pp.h" +#include "ast/scoped_proof.h" +#include "muz/transforms/dl_transforms.h" using namespace spacer; diff --git a/src/muz/spacer/spacer_dl_interface.h b/src/muz/spacer/spacer_dl_interface.h index 809f6fc83..1581762d5 100644 --- a/src/muz/spacer/spacer_dl_interface.h +++ b/src/muz/spacer/spacer_dl_interface.h @@ -19,11 +19,11 @@ Revision History: #ifndef _SPACER_DL_INTERFACE_H_ #define _SPACER_DL_INTERFACE_H_ -#include "lbool.h" -#include "dl_rule.h" -#include "dl_rule_set.h" -#include "dl_engine_base.h" -#include "statistics.h" +#include "util/lbool.h" +#include "muz/base/dl_rule.h" +#include "muz/base/dl_rule_set.h" +#include "muz/base/dl_engine_base.h" +#include "util/statistics.h" namespace datalog { class context; diff --git a/src/muz/spacer/spacer_farkas_learner.cpp b/src/muz/spacer/spacer_farkas_learner.cpp index edee8ba6f..29d238cc9 100644 --- a/src/muz/spacer/spacer_farkas_learner.cpp +++ b/src/muz/spacer/spacer_farkas_learner.cpp @@ -19,21 +19,21 @@ Revision History: --*/ //TODO: reorder, delete unnecessary includes -#include "ast_smt2_pp.h" -#include "array_decl_plugin.h" -#include "bool_rewriter.h" -#include "dl_decl_plugin.h" -#include "for_each_expr.h" -#include "dl_util.h" -#include "rewriter.h" -#include "rewriter_def.h" -#include "spacer_util.h" -#include "spacer_farkas_learner.h" -#include "th_rewriter.h" -#include "ast_ll_pp.h" -#include "proof_utils.h" -#include "reg_decl_plugins.h" -#include "smt_farkas_util.h" +#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/spacer/spacer_util.h" +#include "muz/spacer/spacer_farkas_learner.h" +#include "ast/rewriter/th_rewriter.h" +#include "ast/ast_ll_pp.h" +#include "muz/base/proof_utils.h" +#include "ast/reg_decl_plugins.h" +#include "smt/smt_farkas_util.h" namespace spacer { @@ -437,4 +437,3 @@ bool farkas_learner::is_farkas_lemma(ast_manager& m, expr* e) d->get_num_parameters() >= m.get_num_parents(to_app(e)) + 2; } } - diff --git a/src/muz/spacer/spacer_farkas_learner.h b/src/muz/spacer/spacer_farkas_learner.h index 7a0abf6f5..724b18b73 100644 --- a/src/muz/spacer/spacer_farkas_learner.h +++ b/src/muz/spacer/spacer_farkas_learner.h @@ -20,7 +20,7 @@ Revision History: #ifndef _SPACER_FARKAS_LEARNER_H_ #define _SPACER_FARKAS_LEARNER_H_ -#include "ast.h" +#include "ast/ast.h" namespace spacer { diff --git a/src/muz/spacer/spacer_generalizers.cpp b/src/muz/spacer/spacer_generalizers.cpp index f36983077..9f8757024 100644 --- a/src/muz/spacer/spacer_generalizers.cpp +++ b/src/muz/spacer/spacer_generalizers.cpp @@ -19,12 +19,12 @@ Revision History: --*/ -#include "spacer_context.h" -#include "spacer_generalizers.h" -#include "expr_abstract.h" -#include "var_subst.h" -#include "for_each_expr.h" -#include "obj_equiv_class.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_generalizers.h" +#include "ast/expr_abstract.h" +#include "ast/rewriter/var_subst.h" +#include "ast/for_each_expr.h" +#include "muz/spacer/obj_equiv_class.h" namespace spacer { diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h index 8634f0828..aa542ec04 100644 --- a/src/muz/spacer/spacer_generalizers.h +++ b/src/muz/spacer/spacer_generalizers.h @@ -20,8 +20,8 @@ Revision History: #ifndef _SPACER_GENERALIZERS_H_ #define _SPACER_GENERALIZERS_H_ -#include "spacer_context.h" -#include "arith_decl_plugin.h" +#include "muz/spacer/spacer_context.h" +#include "ast/arith_decl_plugin.h" namespace spacer {