mirror of
https://github.com/Z3Prover/z3
synced 2025-11-01 03:57:51 +00:00
* Make spacer_sem_matcher::reset() public * Add .clang-format for src/muz/spacer * Mark substitution::get_bindings() as const * Fix in spacer_antiunify * Various helper methods in spacer_util Minor functions to compute number of free variables, detect presence of certain sub-expressions, etc. The diff is ugly because of clang-format * Add spacer_cluster for clustering lemmas A cluster of lemmas is a set of lemmas that are all instances of the same pattern, where a pattern is a qff formula with free variables. Currently, the instances are required to be explicit, that is, they are all obtained by substituting concrete values (i.e., numbers) for free variables of the pattern. Lemmas are clustered in cluster_db in each predicate transformer. * Integrate spacer_cluster into spacer_context * Custom clang-format pragmas for spacer_context spacer_context.(cpp|h) are large and have inconsistent formatting. Disable clang-format for them until merge with main z3 branch and re-format. * Computation of convex closure and matrix kernel Various LA functions. The implementations are somewhat preliminary. Convex closure is simplemented via syntactic convex closure procedure. Kernel computation considers many common cases. spacer_arith_kernel_sage implements kernel computation by call external Sage binary. It is used only for debugging and experiments. There is no link dependence on Sage. If desired, it can be removed. * Add spacer_concretize * Utility methods for spacer conjecture rule * Add spacer_expand_bnd_generalizer Generalizes arithmetic inequality literals of the form x <= c, by changing constant c to other constants found in the problem. * Add spacer_global_generalizer Global generalizer checks every new lemma against a cluster of previously learned lemmas, and, if possible, conjectures a new pob, that, when blocked, generalizes multiple existing lemmas. * Remove fp.spacer.print_json option The option is used to dump state of spacer into json for debugging. It has been replaced by `fp.spacer.trace_file` that allows dumping an execution of spacer. The json file can be reconstructed from the trace file elsewhere. * Workaround for segfault in spacer_proof_utils Issue #3 in hgvk94/z3 Segfault in some proof reduction. Avoid by bailing out on reduction. * Revert bug for incomplete models * Use local fresh variables in spacer_global_generalizer * Cleanup of spacer_convex_closure * Allow arbitrary expressions to name cols in convex_closure * WIP: convex closure * WIP: convex closure * Fix bindings order in spacer_global_generalizer The matcher creates substitution using std_order, which is reverse of expected order (variable 0 is last). Adjust the code appropriately for that. * Increase verbosity level for smt_context stats * Dead code in qe_mbp * bug fixes in spacer_global_generalizer::subsumer * Partially remove dependence of size of m_alphas I want m_alphas to potentially be greater than currently used alpha variables. This is helpful for reusing them across multiple calls to convex closure * Subtle bug in kernel computation Coefficient was being passed by reference and, therefore, was being changed indirectly. In the process, updated the code to be more generic to avoid rational computation in the middle of matrix manipulation. * another test for sparse_matrix_ops::kernel * Implementation of matrix kernel using Fraction Free Elimination Ensures that the kernel is int for int matrices. All divisions are exact. * clang-format sparse_matrix_ops.h * another implementation of ffe kernel in sparse_matrix_ops * Re-do arith_kernel and convex_closure * update spacer_global_generalization for new subsumer * remove spacer.gg.use_sage parameter * cleanup of spacer_global_generalizer * Removed dependency on sage * fix in spacer_convex_closure * spacer_sem_matcher: consider an additional semantic matching disabled until it is shown useful * spacer_global_generalizer: improve do_conjecture - if conjecture does not apply to pob, use lemma instead - better normalization - improve debug prints * spacer_conjecture: formatting * spacer_cluster: improve debug prints * spacer_context: improve debug prints * spacer_context: re-queue may pobs enabled even if global re-queue is disabled * spacer_cluster print formatting * reset methods on pob * cleanup of print and local variable names * formatting * reset generalization data once it has been used * refactored extra pob creation during global guidance * fix bug copying sparse matrix into spacer matrix * bug fix in spacer_convex_closure * formatting change in spacer_context * spacer_cluster: get_min_lvl chose level based on pob as well as lemmas * spacer_context: add desired_level to pob desired_level indicates at which level pob should be proved. A pob will be pushed to desired_level if necessary * spacer_context: renamed subsume stats the name of success/failed was switched * spacer_convex_closure: fix prototype of is_congruent_mod() * spacer_convex_closure: hacks in infer_div_pred() * spacer_util: do not expand literals with mod By default, equality literal t=p is expanded into t<=p && t>=p Disable the expansion in case t contains 'mod' operator since such expansion is usually not helpful for divisibility * spacer_util: rename m_util into m_arith * spacer_util: cleanup normalize() * spacer_util: formatting * spacer_context: formatting cleanup on subsume and conjecture * spacer_context: fix handling may pobs when abs_weakness is enabled A pob might be undef, so weakness must be bumped up * spacer_arith_kernel: enhance debug print * spacer_global_generalizer: improve matching on conjecture * spacer_global_generalizer: set desired level on conjecture pob * spacer_global_generalizer: debug print * spacer_global_generalizer: set min level on new pobs the new level should not be higher than the pob that was generalized * spacer_global_generalizer: do no re-create closed pobs If a generalized pob exist and closed, do not re-create it. * spacer_context: normalize twice * spacer_context: forward propagate only same kind of pobs * sketch of inductive generalizer A better implementation of inductive generalizer that in addition to dropping literals also attempts to weaken them. Current implementation is a sketch to be extended based on examples/requirements. * fix ordering in spacer_cluster_util * fix resetting of substitution matcher in spacer_conjecture Old code would forget to reset the substitution provided to the sem_matcher. Thus, if the substitution was matched once (i.e., one literal of interest is found), no other literal would be matched. * add spacer_util is_normalized() method used for debugging only * simplify normalization of pob expressions pob expressions are normalized to increase syntactic matching. Some of the normalization rules seem out of place, so removing them for now. * fix in spacer_global_generalizer If conjecture fails, do not try other generalization strategies -- they will not apply. * fix in spacer_context do not check that may pob is blocked by existing lemmas. It is likely to be blocked. Our goal is to block it again and generalize to a new lemma. This can be further improved by moving directly to generalization when pob is blocked by existing lemmas... Co-authored-by: hgvk94 <hgvk94@gmail.com>
1360 lines
47 KiB
C++
1360 lines
47 KiB
C++
/**++
|
|
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:
|
|
|
|
--*/
|
|
|
|
#pragma once
|
|
// clang-format off
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <queue>
|
|
|
|
#include "muz/spacer/spacer_cluster.h"
|
|
#include "muz/spacer/spacer_manager.h"
|
|
#include "muz/spacer/spacer_prop_solver.h"
|
|
#include "muz/spacer/spacer_sem_matcher.h"
|
|
#include "util/scoped_ptr_vector.h"
|
|
|
|
#include "muz/base/fp_params.hpp"
|
|
|
|
namespace datalog {
|
|
class rule_set;
|
|
class context;
|
|
}; // namespace datalog
|
|
|
|
namespace spacer {
|
|
|
|
class model_search;
|
|
|
|
class pred_transformer;
|
|
class derivation;
|
|
class pob_queue;
|
|
class context;
|
|
class lemma_cluster;
|
|
class lemma_cluster_finder;
|
|
class lemma_global_generalizer;
|
|
|
|
typedef obj_map<datalog::rule const, app_ref_vector *> rule2inst;
|
|
typedef obj_map<func_decl, pred_transformer *> decl2rel;
|
|
|
|
class pob;
|
|
typedef ref<pob> pob_ref;
|
|
typedef sref_vector<pob> pob_ref_vector;
|
|
typedef sref_buffer<pob> pob_ref_buffer;
|
|
|
|
class reach_fact;
|
|
typedef ref<reach_fact> reach_fact_ref;
|
|
typedef sref_vector<reach_fact> reach_fact_ref_vector;
|
|
|
|
class lemma;
|
|
using lemma_ref = ref<lemma>;
|
|
using lemma_ref_vector = sref_vector<lemma>;
|
|
|
|
class reach_fact {
|
|
unsigned m_ref_count;
|
|
|
|
expr_ref m_fact;
|
|
ptr_vector<app> m_aux_vars;
|
|
|
|
const datalog::rule &m_rule;
|
|
reach_fact_ref_vector m_justification;
|
|
|
|
// variable used to tag this reach fact in an incremental disjunction
|
|
app_ref m_tag;
|
|
|
|
bool m_init;
|
|
|
|
public:
|
|
reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact,
|
|
const ptr_vector<app> &aux_vars, bool init = false)
|
|
: m_ref_count(0), m_fact(fact, m), m_aux_vars(aux_vars), m_rule(rule),
|
|
m_tag(m), m_init(init) {}
|
|
reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact,
|
|
bool init = false)
|
|
: m_ref_count(0), m_fact(fact, m), m_rule(rule), m_tag(m),
|
|
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<app> &aux_vars() { return m_aux_vars; }
|
|
|
|
app *tag() const {
|
|
SASSERT(m_tag);
|
|
return m_tag;
|
|
}
|
|
void set_tag(app *tag) { m_tag = tag; }
|
|
|
|
void inc_ref() { ++m_ref_count; }
|
|
void dec_ref() {
|
|
SASSERT(m_ref_count > 0);
|
|
--m_ref_count;
|
|
if (m_ref_count == 0) { dealloc(this); }
|
|
}
|
|
};
|
|
|
|
// a lemma
|
|
class lemma {
|
|
// clang-format off
|
|
unsigned m_ref_count;
|
|
|
|
ast_manager &m;
|
|
expr_ref m_body;
|
|
expr_ref_vector m_cube;
|
|
app_ref_vector m_zks;
|
|
app_ref_vector m_bindings;
|
|
pob_ref m_pob;
|
|
model_ref m_ctp; // counter-example to pushing
|
|
unsigned m_lvl; // current level of the lemma
|
|
unsigned m_init_lvl; // level at which lemma was created
|
|
unsigned m_bumped:16;
|
|
unsigned m_weakness:16;
|
|
unsigned m_external:1; // external lemma from another solver
|
|
unsigned m_blocked:1; // blocked by CTP
|
|
unsigned m_background:1; // background assumed fact
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
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; }
|
|
|
|
model_ref &get_ctp() { return m_ctp; }
|
|
bool has_ctp() { return !is_inductive() && m_ctp; }
|
|
void set_ctp(model_ref &v) { m_ctp = v; }
|
|
void reset_ctp() { m_ctp.reset(); }
|
|
|
|
void bump() { m_bumped++; }
|
|
unsigned get_bumped() { return m_bumped; }
|
|
|
|
expr *get_expr();
|
|
bool is_false();
|
|
expr_ref_vector const &get_cube();
|
|
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 weakness() { return m_weakness; }
|
|
|
|
void add_skolem(app *zk, app *b);
|
|
|
|
void set_external(bool ext) { m_external = ext; }
|
|
bool external() { return m_external; }
|
|
|
|
void set_background(bool v) { m_background = v; }
|
|
bool is_background() { return m_background; }
|
|
|
|
bool is_blocked() { return m_blocked; }
|
|
void set_blocked(bool v) { m_blocked = v; }
|
|
|
|
bool is_inductive() const { return is_infty_level(m_lvl); }
|
|
unsigned level() const { return m_lvl; }
|
|
unsigned init_level() const { return m_init_lvl; }
|
|
void set_level(unsigned lvl);
|
|
app_ref_vector &get_bindings() { return m_bindings; }
|
|
bool has_binding(app_ref_vector const &binding);
|
|
void add_binding(app_ref_vector const &binding);
|
|
void instantiate(expr *const *exprs, expr_ref &result, expr *e = nullptr);
|
|
void mk_insts(expr_ref_vector &inst, expr *e = nullptr);
|
|
bool is_ground() { return !is_quantifier(get_expr()); }
|
|
|
|
void inc_ref() { ++m_ref_count; }
|
|
void dec_ref() {
|
|
SASSERT(m_ref_count > 0);
|
|
--m_ref_count;
|
|
if (m_ref_count == 0) { dealloc(this); }
|
|
}
|
|
};
|
|
|
|
struct lemma_lt_proc {
|
|
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 {
|
|
// clang-format off
|
|
unsigned m_num_propagations; // num of times lemma is pushed higher
|
|
unsigned m_num_invariants; // num of infty lemmas found
|
|
unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing
|
|
unsigned m_num_is_invariant; // num of times lemmas are pushed
|
|
unsigned m_num_lemma_level_jump; // lemma learned at higher level than
|
|
// expected
|
|
unsigned m_num_reach_queries;
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
stats() { reset(); }
|
|
void reset() { memset(this, 0, sizeof(*this)); }
|
|
};
|
|
|
|
/// manager of the lemmas in all the frames
|
|
#include "muz/spacer/spacer_legacy_frames.h"
|
|
class frames {
|
|
private:
|
|
// clang-format off
|
|
pred_transformer &m_pt; // parent pred_transformer
|
|
lemma_ref_vector m_pinned_lemmas; // all created lemmas
|
|
lemma_ref_vector m_lemmas; // active lemmas
|
|
lemma_ref_vector m_bg_invs; // background (assumed) invariants
|
|
unsigned m_size; // num of frames
|
|
|
|
bool m_sorted; // true if m_lemmas is sorted by m_lt
|
|
lemma_lt_proc m_lt; // sort order for m_lemmas
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
void sort();
|
|
|
|
public:
|
|
frames(pred_transformer &pt) : m_pt(pt), m_size(0), m_sorted(true) {}
|
|
void simplify_formulas();
|
|
|
|
pred_transformer &pt() const { return m_pt; }
|
|
const lemma_ref_vector &lemmas() const { return m_lemmas; }
|
|
|
|
void get_frame_lemmas(unsigned level, expr_ref_vector &out) const {
|
|
for (auto &lemma : m_lemmas) {
|
|
if (lemma->level() == level) {
|
|
out.push_back(lemma->get_expr());
|
|
}
|
|
}
|
|
}
|
|
void get_frame_geq_lemmas(unsigned level, expr_ref_vector &out,
|
|
bool with_bg = false) const {
|
|
for (auto &lemma : m_lemmas) {
|
|
if (lemma->level() >= level) {
|
|
out.push_back(lemma->get_expr());
|
|
}
|
|
}
|
|
if (with_bg) {
|
|
for (auto &lemma : m_bg_invs) out.push_back(lemma->get_expr());
|
|
}
|
|
}
|
|
|
|
void get_frame_all_lemmas(lemma_ref_vector &out,
|
|
bool with_bg = false) const {
|
|
for (auto &lemma : m_lemmas) { out.push_back(lemma); }
|
|
if (with_bg) {
|
|
for (auto &lemma : m_bg_invs) out.push_back(lemma);
|
|
}
|
|
}
|
|
const lemma_ref_vector &get_bg_invs() const { return m_bg_invs; }
|
|
unsigned size() const { return m_size; }
|
|
unsigned lemma_size() const { return m_lemmas.size(); }
|
|
unsigned bg_invs_size() const { return m_bg_invs.size(); }
|
|
|
|
void add_frame() { m_size++; }
|
|
void inherit_frames(frames &other) {
|
|
for (auto &other_lemma : other.m_lemmas) {
|
|
lemma_ref new_lemma =
|
|
alloc(lemma, m_pt.get_ast_manager(),
|
|
other_lemma->get_expr(), other_lemma->level());
|
|
new_lemma->add_binding(other_lemma->get_bindings());
|
|
add_lemma(new_lemma.get());
|
|
}
|
|
m_sorted = false;
|
|
m_bg_invs.append(other.m_bg_invs);
|
|
}
|
|
|
|
bool add_lemma(lemma *new_lemma);
|
|
void propagate_to_infinity(unsigned level);
|
|
bool propagate_to_next_level(unsigned level);
|
|
};
|
|
|
|
/**
|
|
manager of proof-obligations (pob_manager)
|
|
|
|
Pobs are determined uniquely by their post-condition and a parent pob.
|
|
They are managed by pob_manager and remain live through the
|
|
life of the manager
|
|
*/
|
|
class pob_manager {
|
|
// a buffer that contains space for one pob and allocates more
|
|
// space if needed
|
|
typedef ptr_buffer<pob, 1> pob_buffer;
|
|
// Type for the map from post-conditions to pobs. The common
|
|
// case is that each post-condition corresponds to a single
|
|
// pob. Other cases are handled by expanding the buffer
|
|
typedef obj_map<expr, pob_buffer> expr2pob_buffer;
|
|
|
|
// parent predicate transformer
|
|
pred_transformer &m_pt;
|
|
|
|
// map from post-conditions to pobs
|
|
expr2pob_buffer m_pobs;
|
|
|
|
// a store
|
|
pob_ref_vector m_pinned;
|
|
|
|
public:
|
|
pob_manager(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);
|
|
}
|
|
unsigned size() const { return m_pinned.size(); }
|
|
|
|
pob *find_pob(pob *parent, expr *post);
|
|
};
|
|
|
|
class pt_rule {
|
|
// clang-format off
|
|
const datalog::rule &m_rule;
|
|
expr_ref m_trans; // ground version of m_rule
|
|
ptr_vector<app> m_auxs; // auxiliary variables in m_trans
|
|
app_ref_vector m_reps; // map from fv in m_rule to ground constants
|
|
app_ref m_tag; // a unique tag for the rule
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
public:
|
|
pt_rule(ast_manager &m, const datalog::rule &r)
|
|
: m_rule(r), m_trans(m), m_reps(m), m_tag(m) {}
|
|
|
|
const datalog::rule &rule() const { return m_rule; }
|
|
|
|
void set_tag(expr *tag) {
|
|
SASSERT(is_app(tag));
|
|
set_tag(to_app(tag));
|
|
}
|
|
void set_tag(app *tag) { m_tag = tag; }
|
|
app *tag() const { return m_tag; }
|
|
ptr_vector<app> &auxs() { return m_auxs; }
|
|
void set_auxs(ptr_vector<app> &v) {
|
|
m_auxs.reset();
|
|
m_auxs.append(v);
|
|
}
|
|
void set_reps(app_ref_vector &v) {
|
|
m_reps.reset();
|
|
m_reps.append(v);
|
|
}
|
|
|
|
void set_trans(expr_ref &v) { m_trans = v; }
|
|
expr *trans() const { return m_trans; }
|
|
bool is_init() const {
|
|
return m_rule.get_uninterpreted_tail_size() == 0;
|
|
}
|
|
};
|
|
|
|
class pt_rules {
|
|
typedef obj_map<datalog::rule const, pt_rule *> rule2ptrule;
|
|
typedef obj_map<const expr, pt_rule *> tag2ptrule;
|
|
typedef rule2ptrule::iterator iterator;
|
|
rule2ptrule m_rules;
|
|
tag2ptrule m_tags;
|
|
|
|
public:
|
|
~pt_rules() {
|
|
for (auto &kv : m_rules) { dealloc(kv.m_value); }
|
|
}
|
|
|
|
bool find_by_rule(const datalog::rule &r, pt_rule *&ptr) {
|
|
return m_rules.find(&r, ptr);
|
|
}
|
|
bool find_by_tag(const expr *tag, pt_rule *&ptr) {
|
|
return m_tags.find(tag, ptr);
|
|
}
|
|
pt_rule &mk_rule(ast_manager &m, const datalog::rule &r) {
|
|
return mk_rule(pt_rule(m, r));
|
|
}
|
|
pt_rule &mk_rule(const pt_rule &v);
|
|
void set_tag(expr *tag, pt_rule &v) {
|
|
pt_rule *p;
|
|
VERIFY(find_by_rule(v.rule(), p));
|
|
p->set_tag(tag);
|
|
m_tags.insert(tag, p);
|
|
}
|
|
|
|
bool empty() { return m_rules.empty(); }
|
|
iterator begin() { return m_rules.begin(); }
|
|
iterator end() { return m_rules.end(); }
|
|
};
|
|
|
|
/// Clusters of lemmas
|
|
class cluster_db {
|
|
sref_vector<lemma_cluster> m_clusters;
|
|
unsigned m_max_cluster_size;
|
|
|
|
public:
|
|
cluster_db() : m_max_cluster_size(0) {}
|
|
unsigned get_max_cluster_size() const { return m_max_cluster_size; }
|
|
|
|
/// Return the smallest cluster than can contain \p lemma
|
|
lemma_cluster *can_contain(const lemma_ref &lemma) {
|
|
unsigned sz = UINT_MAX;
|
|
lemma_cluster *res = nullptr;
|
|
for (auto *c : m_clusters) {
|
|
if (c->get_gas() > 0 && c->get_size() < sz &&
|
|
c->can_contain(lemma)) {
|
|
res = c;
|
|
sz = res->get_size();
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool contains(const lemma_ref &lemma) {
|
|
for (auto *c : m_clusters) {
|
|
if (c->contains(lemma)) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// The number of clusters with pattern \p pattern
|
|
unsigned clstr_count(const expr_ref &pattern) {
|
|
unsigned count = 0;
|
|
for (auto c : m_clusters) {
|
|
if (c->get_pattern() == pattern) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
lemma_cluster *mk_cluster(const expr_ref &pattern) {
|
|
m_clusters.push_back(alloc(lemma_cluster, pattern));
|
|
return m_clusters.back();
|
|
}
|
|
|
|
/// Return the smallest cluster containing \p lemma
|
|
lemma_cluster *get_cluster(const lemma_ref &lemma) {
|
|
unsigned sz = UINT_MAX;
|
|
lemma_cluster *res = nullptr;
|
|
for (auto *c : m_clusters) {
|
|
if (c->get_size() < sz && c->contains(lemma)) {
|
|
res = c;
|
|
sz = res->get_size();
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
lemma_cluster *get_cluster(const expr *pattern) {
|
|
for (lemma_cluster *lc : m_clusters) {
|
|
if (lc->get_pattern().get() == pattern) return lc;
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
// clang-format off
|
|
manager& pm; // spacer::manager
|
|
ast_manager& m; // ast_manager
|
|
context& ctx; // spacer::context
|
|
|
|
func_decl_ref m_head; // predicate
|
|
func_decl_ref_vector m_sig; // signature
|
|
ptr_vector<pred_transformer> m_use; // places where 'this' is referenced.
|
|
pt_rules m_pt_rules; // pt rules used to derive transformer
|
|
ptr_vector<datalog::rule> m_rules; // rules used to derive transformer
|
|
scoped_ptr<prop_solver> m_solver; // solver context
|
|
ref<solver> m_reach_solver; // context for reachability facts
|
|
pob_manager m_pobs; // proof obligations created so far
|
|
frames m_frames; // frames with lemmas
|
|
reach_fact_ref_vector m_reach_facts; // reach facts
|
|
unsigned m_rf_init_sz; // number of reach fact from INIT
|
|
expr_ref_vector m_transition_clause; // extra clause for trans
|
|
expr_ref m_transition; // transition relation
|
|
expr_ref m_init; // initial condition
|
|
app_ref m_extend_lit0; // first literal used to extend initial state
|
|
app_ref m_extend_lit; // current literal to extend initial state
|
|
bool m_all_init; // true if the pt has no uninterpreted body in any rule
|
|
ptr_vector<func_decl> m_predicates; // temp vector used with find_predecessors()
|
|
stats m_stats;
|
|
stopwatch m_initialize_watch;
|
|
stopwatch m_must_reachable_watch;
|
|
stopwatch m_ctp_watch;
|
|
stopwatch m_mbp_watch;
|
|
bool m_has_quantified_frame; // True when a quantified lemma is in the frame
|
|
cluster_db m_cluster_db;
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
void init_sig();
|
|
app_ref mk_extend_lit();
|
|
void ensure_level(unsigned level);
|
|
void add_lemma_core(lemma *lemma, bool ground_only = false);
|
|
void add_lemma_from_child(pred_transformer &child, lemma *lemma,
|
|
unsigned lvl, bool ground_only = false);
|
|
|
|
void mk_assumptions(func_decl *head, expr *fml, expr_ref_vector &result);
|
|
|
|
// Initialization
|
|
void init_rules(decl2rel const &pts);
|
|
void init_rule(decl2rel const &pts, datalog::rule const &rule);
|
|
void init_atom(decl2rel const &pts, app *atom, app_ref_vector &var_reprs,
|
|
expr_ref_vector &side, unsigned tail_idx);
|
|
|
|
void simplify_formulas(tactic &tac, expr_ref_vector &fmls);
|
|
|
|
void add_premises(decl2rel const &pts, unsigned lvl, datalog::rule &rule,
|
|
expr_ref_vector &r);
|
|
|
|
app_ref mk_fresh_rf_tag();
|
|
|
|
// get tagged formulae of all of the background invariants for all of the
|
|
// predecessors of the current transformer
|
|
void get_pred_bg_invs(expr_ref_vector &out);
|
|
const lemma_ref_vector &get_bg_invs() const {
|
|
return m_frames.get_bg_invs();
|
|
}
|
|
|
|
public:
|
|
pred_transformer(context &ctx, manager &pm, func_decl *head);
|
|
|
|
inline bool use_native_mbp();
|
|
bool mk_mdl_rf_consistent(const datalog::rule *r, model &mdl);
|
|
reach_fact *get_rf(expr *v) {
|
|
for (auto *rf : m_reach_facts) {
|
|
if (v == rf->get()) { return rf; }
|
|
}
|
|
return nullptr;
|
|
}
|
|
void find_predecessors(datalog::rule const &r,
|
|
ptr_vector<func_decl> &predicates) const;
|
|
|
|
void add_rule(datalog::rule *r) { m_rules.push_back(r); }
|
|
void add_use(pred_transformer *pt) {
|
|
if (!m_use.contains(pt)) { m_use.insert(pt); }
|
|
}
|
|
void initialize(decl2rel const &pts);
|
|
|
|
func_decl *head() const { return m_head; }
|
|
ptr_vector<datalog::rule> const &rules() const { return m_rules; }
|
|
func_decl *sig(unsigned i) const { return m_sig[i]; } // signature
|
|
func_decl *const *sig() { return m_sig.data(); }
|
|
unsigned sig_size() const { return m_sig.size(); }
|
|
expr *transition() const { return m_transition; }
|
|
expr *init() const { return m_init; }
|
|
expr *rule2tag(datalog::rule const *r) {
|
|
pt_rule *p;
|
|
return m_pt_rules.find_by_rule(*r, p) ? p->tag() : nullptr;
|
|
}
|
|
unsigned get_num_levels() const { return m_frames.size(); }
|
|
expr_ref get_cover_delta(func_decl *p_orig, int level);
|
|
void add_cover(unsigned level, expr *property, bool bg = false);
|
|
expr_ref get_reachable();
|
|
|
|
std::ostream &display(std::ostream &strm) const;
|
|
|
|
void collect_statistics(statistics &st) const;
|
|
void reset_statistics();
|
|
|
|
bool is_must_reachable(expr *state, model_ref *model = nullptr);
|
|
/// \brief Returns reachability fact active in the given model
|
|
/// all determines whether initial reachability facts are included as well
|
|
reach_fact *get_used_rf(model &mdl, bool all = true);
|
|
/// \brief Returns reachability fact active in the origin of the given model
|
|
reach_fact *get_used_origin_rf(model &mdl, unsigned oidx);
|
|
/// \brief Collects all the reachable facts used in mdl
|
|
void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector &res);
|
|
void get_all_used_rf(model &mdl, reach_fact_ref_vector &res);
|
|
expr_ref get_origin_summary(model &mdl, unsigned level, unsigned oidx,
|
|
bool must, const ptr_vector<app> **aux);
|
|
|
|
bool is_ctp_blocked(lemma *lem);
|
|
const datalog::rule *find_rule(model &mdl);
|
|
const datalog::rule *find_rule(model &mev, bool &is_concrete,
|
|
bool_vector &reach_pred_used,
|
|
unsigned &num_reuse_reach);
|
|
expr *get_transition(datalog::rule const &r) {
|
|
pt_rule *p;
|
|
return m_pt_rules.find_by_rule(r, p) ? p->trans() : nullptr;
|
|
}
|
|
ptr_vector<app> &get_aux_vars(datalog::rule const &r) {
|
|
pt_rule *p = nullptr;
|
|
VERIFY(m_pt_rules.find_by_rule(r, p));
|
|
return p->auxs();
|
|
}
|
|
|
|
bool propagate_to_next_level(unsigned level);
|
|
void propagate_to_infinity(unsigned level);
|
|
/// \brief Add a lemma to the current context and all users
|
|
bool add_lemma(expr *e, unsigned lvl, bool bg);
|
|
bool add_lemma(lemma *lem) { return m_frames.add_lemma(lem); }
|
|
expr *get_reach_case_var(unsigned idx) const;
|
|
bool has_rfs() const { return !m_reach_facts.empty(); }
|
|
|
|
/// initialize reachability facts using initial rules
|
|
void init_rfs();
|
|
reach_fact *mk_rf(pob &n, model &mdl, const datalog::rule &r);
|
|
void add_rf(reach_fact *fact, bool force = false); // add reachability fact
|
|
reach_fact *get_last_rf() const { return m_reach_facts.back(); }
|
|
expr *get_last_rf_tag() const;
|
|
|
|
pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post,
|
|
app_ref_vector const &b) {
|
|
return m_pobs.mk_pob(parent, level, depth, post, b);
|
|
}
|
|
pob *find_pob(pob *parent, expr *post) {
|
|
return m_pobs.find_pob(parent, post);
|
|
}
|
|
|
|
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, bool_vector &reach_pred_used,
|
|
unsigned& num_reuse_reach, bool use_iuc = true);
|
|
bool is_invariant(unsigned level, lemma *lem, unsigned &solver_level,
|
|
expr_ref_vector* core = nullptr);
|
|
|
|
bool is_invariant(unsigned level, expr *lem, unsigned &solver_level,
|
|
expr_ref_vector *core = nullptr) {
|
|
// XXX only needed for legacy_frames to compile
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
|
|
bool check_inductive(unsigned level, expr_ref_vector &state,
|
|
unsigned &assumes_level, unsigned weakness = UINT_MAX);
|
|
|
|
expr_ref get_formulas(unsigned level, bool bg = false) const;
|
|
|
|
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_lemmas(pred_transformer &other);
|
|
|
|
void ground_free_vars(expr *e, app_ref_vector &vars,
|
|
ptr_vector<app> &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, model_ref *model = nullptr);
|
|
/// \brief Returns true if the obligation is already blocked by current
|
|
/// quantified lemmas
|
|
bool is_qblocked(pob &n);
|
|
|
|
/// \brief interface to Model Based Projection
|
|
void mbp(app_ref_vector &vars, expr_ref &fml, model &mdl,
|
|
bool reduce_all_selects, bool force = false);
|
|
|
|
void updt_solver(prop_solver *solver);
|
|
|
|
void updt_solver_with_lemmas(prop_solver *solver,
|
|
const pred_transformer &pt, app *rule_tag,
|
|
unsigned pos);
|
|
// exposing ACTIVE lemmas (alternatively, one can expose `m_pinned_lemmas`
|
|
// for ALL lemmas)
|
|
void get_all_lemmas(lemma_ref_vector &out, bool with_bg = false) const {
|
|
m_frames.get_frame_all_lemmas(out, with_bg);
|
|
}
|
|
void update_solver_with_rfs(prop_solver *solver, const pred_transformer &pt,
|
|
app *rule_tag, unsigned pos);
|
|
|
|
lemma_cluster *mk_cluster(const expr_ref &pattern) {
|
|
return m_cluster_db.mk_cluster(pattern);
|
|
}
|
|
|
|
// Checks whether \p lemma is in any existing cluster
|
|
bool clstr_contains(const lemma_ref &lemma) {
|
|
return m_cluster_db.contains(lemma);
|
|
}
|
|
|
|
/// The number of clusters with pattern \p pattern
|
|
unsigned clstr_count(const expr_ref &pattern) {
|
|
return m_cluster_db.clstr_count(pattern);
|
|
}
|
|
|
|
/// Checks whether \p lemma matches any cluster
|
|
lemma_cluster *clstr_match(const lemma_ref &lemma) {
|
|
lemma_cluster *res = m_cluster_db.get_cluster(lemma);
|
|
if (!res) res = m_cluster_db.can_contain(lemma);
|
|
return res;
|
|
}
|
|
|
|
/// Returns a cluster with pattern \p pattern
|
|
lemma_cluster *get_cluster(const expr *pattern) {
|
|
return m_cluster_db.get_cluster(pattern);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A proof obligation.
|
|
*/
|
|
class pob {
|
|
// clang-format off
|
|
// TBD: remove this
|
|
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:16;
|
|
unsigned m_depth:16;
|
|
|
|
unsigned m_desired_level:16;
|
|
|
|
/// whether a concrete answer to the post is found
|
|
unsigned m_open:1;
|
|
/// whether to use farkas generalizer to construct a lemma blocking this
|
|
/// node
|
|
unsigned m_use_farkas:1;
|
|
/// true if this pob is in pob_queue
|
|
unsigned m_in_queue:1;
|
|
// true if this pob is a conjecture
|
|
unsigned m_is_conjecture:1;
|
|
// should do local generalizations on pob
|
|
unsigned m_enable_local_gen:1;
|
|
// should concretize cube
|
|
unsigned m_enable_concretize:1;
|
|
// is a subsume pob
|
|
unsigned m_is_subsume:1;
|
|
// should apply expand bnd generalization on pob
|
|
unsigned m_enable_expand_bnd_gen:1;
|
|
|
|
unsigned m_weakness;
|
|
/// derivation representing the position of this node in the parent's rule
|
|
scoped_ptr<derivation> m_derivation;
|
|
|
|
/// pobs created as children of this pob (at any time, not
|
|
/// necessarily currently active)
|
|
ptr_vector<pob> m_kids;
|
|
// lemmas created to block this pob (at any time, not necessarily active)
|
|
ptr_vector<lemma> m_lemmas;
|
|
|
|
unsigned m_blocked_lvl;
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
// pattern identified for one of its lemmas
|
|
expr_ref m_concretize_pat;
|
|
|
|
// gas decides how much time is spent in blocking this (may) pob
|
|
unsigned m_gas;
|
|
|
|
// additional data used by global (and other) generalizations
|
|
scoped_ptr<pob> m_data;
|
|
public:
|
|
pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0,
|
|
bool add_to_parent = true);
|
|
|
|
// no copy constructor
|
|
pob(const pob&) = delete;
|
|
// no move constructor
|
|
pob(pob &&) = delete;
|
|
|
|
~pob() {
|
|
if (m_parent) { m_parent->erase_child(*this); }
|
|
}
|
|
|
|
void set_data(pob* v) { m_data = v; }
|
|
void reset_data() { set_data(nullptr); }
|
|
pob* get_data() { return m_data.get(); }
|
|
bool has_data() { return m_data; }
|
|
|
|
// TBD: move into constructor and make private
|
|
void set_post(expr *post, app_ref_vector const &binding);
|
|
void set_post(expr *post);
|
|
|
|
unsigned weakness() { return m_weakness; }
|
|
void bump_weakness() { m_weakness++; }
|
|
void reset_weakness() { m_weakness = 0; }
|
|
|
|
void inc_level() {
|
|
SASSERT(!is_in_queue());
|
|
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 (nullptr); }
|
|
/// detaches derivation from the node without deallocating
|
|
derivation* detach_derivation() { return m_derivation.detach (); }
|
|
|
|
pob* parent() const { return m_parent.get (); }
|
|
|
|
bool is_conjecture() const { return m_is_conjecture; }
|
|
void set_conjecture(bool v = true) { m_is_conjecture = v; }
|
|
|
|
void disable_expand_bnd_gen() { m_enable_expand_bnd_gen = false; }
|
|
bool is_expand_bnd_enabled() { return m_enable_expand_bnd_gen; }
|
|
void set_expand_bnd(bool v = true) { m_enable_expand_bnd_gen = v; }
|
|
void set_concretize_pattern(const expr_ref &pattern) { m_concretize_pat = pattern; }
|
|
const expr_ref &get_concretize_pattern() const { return m_concretize_pat; }
|
|
bool is_subsume() const { return m_is_subsume; }
|
|
void set_subsume(bool v = true) { m_is_subsume = v; }
|
|
bool is_may_pob() const { return is_subsume() || is_conjecture(); }
|
|
unsigned get_gas() const { return m_gas; }
|
|
void set_gas(unsigned n) { m_gas = n; }
|
|
|
|
bool is_local_gen_enabled() const { return m_enable_local_gen; }
|
|
void disable_local_gen() { m_enable_local_gen = false; }
|
|
void get_post_simplified(expr_ref_vector &res);
|
|
bool is_concretize_enabled() const { return m_enable_concretize && m_gas > 0; }
|
|
void set_concretize(bool v = true) { m_enable_concretize = v; }
|
|
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; }
|
|
unsigned desired_level() const { return m_desired_level; }
|
|
void set_desired_level(unsigned v) { m_desired_level = v; }
|
|
unsigned width() const { return m_kids.size(); }
|
|
unsigned blocked_at(unsigned lvl = 0) {
|
|
return (m_blocked_lvl = std::max(lvl, m_blocked_lvl));
|
|
}
|
|
|
|
bool is_in_queue() const { return m_in_queue; }
|
|
void set_in_queue(bool v) { m_in_queue = v; }
|
|
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(); }
|
|
|
|
bool is_closed() const { return !m_open; }
|
|
void close();
|
|
|
|
const ptr_vector<pob> &children() const { return m_kids; }
|
|
void add_child(pob &v) { m_kids.push_back(&v); }
|
|
void erase_child(pob &v) { m_kids.erase(&v); }
|
|
|
|
const ptr_vector<lemma> &lemmas() const { return m_lemmas; }
|
|
void add_lemma(lemma *new_lemma) { m_lemmas.push_back(new_lemma); }
|
|
|
|
bool is_ground() const { return m_binding.empty(); }
|
|
unsigned get_free_vars_size() const { return m_binding.size(); }
|
|
app_ref_vector const &get_binding() const { return m_binding; }
|
|
/*
|
|
* Returns a map from variable id to skolems that implicitly
|
|
* represent them in the pob. Note that only some (or none) of the
|
|
* skolems returned actually appear in the post of the pob.
|
|
*/
|
|
void get_skolems(app_ref_vector &v);
|
|
|
|
void on_expand() {
|
|
if (m_parent.get()) { m_parent.get()->on_expand(); }
|
|
}
|
|
void off_expand() {
|
|
if (m_parent.get()) { m_parent.get()->off_expand(); }
|
|
}
|
|
|
|
void inc_ref() { ++m_ref_count; }
|
|
void dec_ref() {
|
|
--m_ref_count;
|
|
if (m_ref_count == 0) { dealloc(this); }
|
|
}
|
|
|
|
std::ostream &display(std::ostream &out, bool full = false) const;
|
|
class on_expand_event {
|
|
pob &m_p;
|
|
|
|
public:
|
|
on_expand_event(pob &p) : m_p(p) { m_p.on_expand(); }
|
|
~on_expand_event() { m_p.off_expand(); }
|
|
};
|
|
};
|
|
|
|
inline std::ostream &operator<<(std::ostream &out, pob const &p) {
|
|
return p.display(out);
|
|
}
|
|
|
|
struct pob_lt_proc {
|
|
bool operator()(const pob *pn1, const pob *pn2) const;
|
|
};
|
|
|
|
struct pob_gt_proc {
|
|
bool operator()(const pob *n1, const pob *n2) const {
|
|
return pob_lt_proc()(n2, n1);
|
|
}
|
|
};
|
|
|
|
/**
|
|
*/
|
|
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<app> *aux_vars = nullptr);
|
|
|
|
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<app> *aux_vars = nullptr);
|
|
};
|
|
|
|
// clang-format off
|
|
/// parent model node
|
|
pob& m_parent;
|
|
|
|
/// the rule corresponding to this derivation
|
|
const datalog::rule &m_rule;
|
|
|
|
/// the premises
|
|
vector<premise> 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;
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
/// -- create next child using given model as the guide
|
|
/// -- returns NULL if there is no next child
|
|
pob *create_next_child(model &mdl);
|
|
/// existentially quantify vars and skolemize the result
|
|
void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res);
|
|
|
|
public:
|
|
derivation(pob &parent, datalog::rule const &rule, expr *trans,
|
|
app_ref_vector const &evars);
|
|
void add_premise(pred_transformer &pt, unsigned oidx, expr *summary,
|
|
bool must, const ptr_vector<app> *aux_vars = nullptr);
|
|
|
|
/// 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 &mdl);
|
|
|
|
/// 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(); }
|
|
pred_transformer &pt() const { return m_parent.pt(); }
|
|
};
|
|
|
|
class pob_queue {
|
|
|
|
typedef std::priority_queue<pob *, std::vector<pob *>, pob_gt_proc>
|
|
pob_queue_ty;
|
|
pob_ref m_root;
|
|
unsigned m_max_level;
|
|
unsigned m_min_depth;
|
|
|
|
pob_queue_ty m_data;
|
|
|
|
public:
|
|
pob_queue() : m_root(nullptr), m_max_level(0), m_min_depth(0) {}
|
|
|
|
void reset();
|
|
pob *top();
|
|
void pop();
|
|
void push(pob &n);
|
|
|
|
void inc_level() {
|
|
SASSERT(!m_data.empty() || m_root);
|
|
m_max_level++;
|
|
m_min_depth++;
|
|
if (m_root && m_data.empty()) {
|
|
SASSERT(!m_root->is_in_queue());
|
|
m_root->set_in_queue(true);
|
|
m_data.push(m_root.get());
|
|
}
|
|
}
|
|
|
|
pob &get_root() const { return *m_root.get(); }
|
|
void set_root(pob &n);
|
|
bool is_root(pob &n) const { return m_root.get() == &n; }
|
|
|
|
unsigned max_level() const { return m_max_level; }
|
|
unsigned min_depth() const { return m_min_depth; }
|
|
size_t size() const { return m_data.size(); }
|
|
};
|
|
|
|
/**
|
|
* Generalizers (strengthens) a lemma
|
|
*/
|
|
class lemma_generalizer {
|
|
protected:
|
|
context &m_ctx;
|
|
|
|
public:
|
|
lemma_generalizer(context &ctx) : m_ctx(ctx) {}
|
|
virtual ~lemma_generalizer() = default;
|
|
virtual void operator()(lemma_ref &lemma) = 0;
|
|
virtual void collect_statistics(statistics &st) const {}
|
|
virtual void reset_statistics() {}
|
|
};
|
|
|
|
class spacer_callback {
|
|
protected:
|
|
context &m_context;
|
|
|
|
public:
|
|
spacer_callback(context &context) : m_context(context) {}
|
|
|
|
virtual ~spacer_callback() = default;
|
|
|
|
context &get_context() { return m_context; }
|
|
|
|
virtual inline bool new_lemma() { return false; }
|
|
|
|
virtual void new_lemma_eh(expr *lemma, unsigned level) {}
|
|
|
|
virtual inline bool predecessor() { return false; }
|
|
|
|
virtual void predecessor_eh() {}
|
|
|
|
virtual inline bool unfold() { return false; }
|
|
|
|
virtual void unfold_eh() {}
|
|
|
|
virtual inline bool propagate() { return false; }
|
|
|
|
virtual void propagate_eh() {}
|
|
};
|
|
|
|
// order in which children are processed
|
|
enum spacer_children_order {
|
|
CO_RULE, // same order as in the rule
|
|
CO_REV_RULE, // reverse order of the rule
|
|
CO_RANDOM // random shuffle
|
|
};
|
|
|
|
class context {
|
|
friend class pred_transformer;
|
|
struct stats {
|
|
unsigned m_num_queries;
|
|
unsigned m_num_reuse_reach;
|
|
unsigned m_max_query_lvl;
|
|
unsigned m_max_depth;
|
|
unsigned m_cex_depth;
|
|
unsigned m_expand_pob_undef;
|
|
unsigned m_num_lemmas;
|
|
unsigned m_num_restarts;
|
|
unsigned m_num_lemmas_imported;
|
|
unsigned m_num_lemmas_discarded;
|
|
unsigned m_num_conj;
|
|
unsigned m_num_conj_success;
|
|
unsigned m_num_conj_failed;
|
|
unsigned m_num_subsume_pobs;
|
|
unsigned m_num_subsume_pob_reachable;
|
|
unsigned m_num_subsume_pob_blckd;
|
|
unsigned m_num_concretize;
|
|
unsigned m_num_pob_ofg;
|
|
unsigned m_non_local_gen;
|
|
stats() { reset(); }
|
|
void reset() { memset(this, 0, sizeof(*this)); }
|
|
};
|
|
|
|
// clang-format off
|
|
// 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;
|
|
|
|
fp_params const& m_params;
|
|
ast_manager& m;
|
|
datalog::context* m_context;
|
|
manager m_pm;
|
|
|
|
// three solver pools for different queries
|
|
scoped_ptr<solver_pool> m_pool0;
|
|
scoped_ptr<solver_pool> m_pool1;
|
|
scoped_ptr<solver_pool> m_pool2;
|
|
|
|
random_gen m_random;
|
|
spacer_children_order m_children_order;
|
|
decl2rel m_rels; // Map from relation predicate to fp-operator.
|
|
func_decl_ref m_query_pred;
|
|
pred_transformer* m_query;
|
|
mutable pob_queue m_pob_queue;
|
|
lbool m_last_result;
|
|
unsigned m_inductive_lvl;
|
|
unsigned m_expanded_lvl;
|
|
ptr_buffer<lemma_generalizer> m_lemma_generalizers;
|
|
lemma_global_generalizer *m_global_gen;
|
|
lemma_generalizer *m_expand_bnd_gen;
|
|
lemma_cluster_finder *m_lmma_cluster;
|
|
stats m_stats;
|
|
model_converter_ref m_mc;
|
|
proof_converter_ref m_pc;
|
|
bool m_use_native_mbp;
|
|
bool m_instantiate;
|
|
bool m_use_qlemmas;
|
|
bool m_weak_abs;
|
|
bool m_use_restarts;
|
|
bool m_simplify_pob;
|
|
bool m_use_euf_gen;
|
|
bool m_use_lim_num_gen;
|
|
bool m_use_ctp;
|
|
bool m_use_inc_clause;
|
|
bool m_use_ind_gen;
|
|
bool m_use_array_eq_gen;
|
|
bool m_validate_lemmas;
|
|
bool m_use_propagate;
|
|
bool m_reset_obligation_queue;
|
|
bool m_push_pob;
|
|
bool m_use_lemma_as_pob;
|
|
bool m_elim_aux;
|
|
bool m_reach_dnf;
|
|
bool m_use_derivations;
|
|
bool m_validate_result;
|
|
bool m_use_eq_prop;
|
|
bool m_ground_pob;
|
|
bool m_q3_qgen;
|
|
bool m_use_gpdr;
|
|
bool m_simplify_formulas_pre;
|
|
bool m_simplify_formulas_post;
|
|
bool m_pdr_bfs;
|
|
bool m_use_bg_invs;
|
|
bool m_global;
|
|
bool m_expand_bnd;
|
|
bool m_gg_conjecture;
|
|
bool m_gg_subsume;
|
|
bool m_gg_concretize;
|
|
bool m_use_iuc;
|
|
unsigned m_push_pob_max_depth;
|
|
unsigned m_max_level;
|
|
unsigned m_restart_initial_threshold;
|
|
unsigned m_blast_term_ite_inflation;
|
|
scoped_ptr_vector<spacer_callback> m_callbacks;
|
|
std::fstream* m_trace_stream;
|
|
// clang-format on
|
|
// clang-format off
|
|
|
|
// Solve using gpdr strategy
|
|
lbool gpdr_solve_core();
|
|
bool gpdr_check_reachability(unsigned lvl, model_search &ms);
|
|
bool gpdr_create_split_children(pob &n, const datalog::rule &r, expr *trans,
|
|
model &mdl, pob_ref_buffer &out);
|
|
|
|
// progress logging
|
|
void log_enter_level(unsigned lvl);
|
|
void log_propagate();
|
|
void log_expand_pob(pob &);
|
|
void log_add_lemma(pred_transformer &, lemma &);
|
|
|
|
// Functions used by search.
|
|
lbool solve_core(unsigned from_lvl = 0);
|
|
bool is_requeue(pob &n);
|
|
bool check_reachability();
|
|
bool propagate(unsigned min_prop_lvl, unsigned max_prop_lvl,
|
|
unsigned full_prop_lvl);
|
|
bool is_reachable(pob &n);
|
|
lbool expand_pob(pob &n, pob_ref_buffer &out);
|
|
bool create_children(pob &n, const datalog::rule &r, model &mdl,
|
|
const bool_vector &reach_pred_used,
|
|
pob_ref_buffer &out);
|
|
|
|
/**
|
|
\brief Retrieve satisfying assignment with explanation.
|
|
*/
|
|
expr_ref mk_sat_answer() const {
|
|
proof_ref pr = get_ground_refutation();
|
|
return expr_ref(pr.get(), pr.get_manager());
|
|
}
|
|
expr_ref mk_unsat_answer() const;
|
|
unsigned get_cex_depth();
|
|
|
|
// Generate inductive property
|
|
void get_level_property(unsigned lvl, expr_ref_vector &res,
|
|
vector<relation_info> &rs,
|
|
bool with_bg = false) const;
|
|
|
|
// Initialization
|
|
void init_lemma_generalizers();
|
|
void reset_lemma_generalizers();
|
|
void inherit_lemmas(const decl2rel &rels);
|
|
void init_global_smt_params();
|
|
void init_rules(datalog::rule_set &rules, decl2rel &transformers);
|
|
// (re)initialize context with new relations
|
|
void init(const decl2rel &rels);
|
|
|
|
bool validate();
|
|
bool check_invariant(unsigned lvl);
|
|
bool check_invariant(unsigned lvl, func_decl *fn);
|
|
|
|
void checkpoint();
|
|
|
|
void simplify_formulas();
|
|
|
|
void predecessor_eh();
|
|
|
|
void updt_params();
|
|
lbool handle_unknown(pob &n, const datalog::rule *r, model &model);
|
|
bool mk_mdl_rf_consistent(model &mdl);
|
|
|
|
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(fp_params const ¶ms, ast_manager &m);
|
|
~context();
|
|
|
|
const fp_params &get_params() const { return m_params; }
|
|
bool use_eq_prop() const { return m_use_eq_prop; }
|
|
bool use_native_mbp() const { return m_use_native_mbp; }
|
|
bool use_ground_pob() const { return m_ground_pob; }
|
|
bool use_instantiate() const { return m_instantiate; }
|
|
bool weak_abs() const { return m_weak_abs; }
|
|
bool use_qlemmas() const { return m_use_qlemmas; }
|
|
bool use_euf_gen() const { return m_use_euf_gen; }
|
|
bool use_lim_num_gen() const { return m_use_lim_num_gen; }
|
|
bool simplify_pob() const { return m_simplify_pob; }
|
|
bool use_ctp() const { return m_use_ctp; }
|
|
bool use_inc_clause() const { return m_use_inc_clause; }
|
|
unsigned blast_term_ite_inflation() const {
|
|
return m_blast_term_ite_inflation;
|
|
}
|
|
bool elim_aux() const { return m_elim_aux; }
|
|
bool reach_dnf() const { return m_reach_dnf; }
|
|
bool use_bg_invs() const { return m_use_bg_invs; }
|
|
bool do_subsume() const { return m_gg_subsume; }
|
|
|
|
ast_manager &get_ast_manager() const { return m; }
|
|
manager &get_manager() { return m_pm; }
|
|
const manager &get_manager() const { return m_pm; }
|
|
decl2rel const &get_pred_transformers() const { return m_rels; }
|
|
pred_transformer &get_pred_transformer(func_decl *p) const {
|
|
return *m_rels.find(p);
|
|
}
|
|
|
|
datalog::context &get_datalog_context() const {
|
|
SASSERT(m_context);
|
|
return *m_context;
|
|
}
|
|
|
|
void update_rules(datalog::rule_set &rules);
|
|
lbool solve(unsigned from_lvl = 0);
|
|
lbool solve_from_lvl(unsigned from_lvl);
|
|
|
|
expr_ref get_answer();
|
|
/**
|
|
* get bottom-up (from query) sequence of ground predicate instances
|
|
* (for e.g. P(0,1,0,0,3)) that together form a ground derivation to query
|
|
*/
|
|
expr_ref get_ground_sat_answer() const;
|
|
proof_ref get_ground_refutation() const;
|
|
void get_rules_along_trace(datalog::rule_ref_vector &rules);
|
|
|
|
void collect_statistics(statistics &st) const;
|
|
void reset_statistics();
|
|
void reset();
|
|
|
|
std::ostream &display(std::ostream &out) const;
|
|
void display_certificate(std::ostream &out) const;
|
|
|
|
pob &get_root() const { return m_pob_queue.get_root(); }
|
|
void set_query(func_decl *q) { m_query_pred = q; }
|
|
void set_unsat() { m_last_result = l_false; }
|
|
void set_model_converter(model_converter_ref &mc) { m_mc = mc; }
|
|
model_converter_ref get_model_converter() { return m_mc; }
|
|
void set_proof_converter(proof_converter_ref &pc) { m_pc = pc; }
|
|
scoped_ptr_vector<spacer_callback> &callbacks() { return m_callbacks; }
|
|
|
|
unsigned get_num_levels(func_decl *p);
|
|
|
|
expr_ref get_cover_delta(int level, func_decl *p_orig, func_decl *p);
|
|
void add_cover(int level, func_decl *pred, expr *property, bool bg = false);
|
|
expr_ref get_reachable(func_decl *p);
|
|
void add_invariant(func_decl *pred, expr *property);
|
|
model_ref get_model();
|
|
proof_ref get_proof() const { return get_ground_refutation(); }
|
|
|
|
expr_ref get_constraints(unsigned lvl);
|
|
void add_constraint(expr *c, unsigned lvl);
|
|
|
|
void new_lemma_eh(pred_transformer &pt, lemma *lem);
|
|
void new_pob_eh(pob *p);
|
|
|
|
bool is_inductive();
|
|
|
|
// close all parents of may pob when gas runs out
|
|
void close_all_may_parents(pob_ref node);
|
|
|
|
// three different solvers with three different sets of parameters
|
|
// different solvers are used for different types of queries in spacer
|
|
solver *mk_solver0() { return m_pool0->mk_solver(); }
|
|
solver *mk_solver1() { return m_pool1->mk_solver(); }
|
|
solver *mk_solver2() { return m_pool2->mk_solver(); }
|
|
};
|
|
|
|
inline bool pred_transformer::use_native_mbp() { return ctx.use_native_mbp(); }
|
|
} // namespace spacer
|