mirror of
https://github.com/Z3Prover/z3
synced 2026-07-20 14:05:50 +00:00
Merge remote-tracking branch 'origin/master' into c3
This commit is contained in:
commit
706f62286e
199 changed files with 11004 additions and 4584 deletions
|
|
@ -5097,7 +5097,7 @@ namespace seq {
|
|||
svector<sat::literal>& mem_literals) const {
|
||||
SASSERT(m_root);
|
||||
const auto deps = collect_conflict_deps();
|
||||
vector<dep_source> vs;
|
||||
vector<dep_source, false> vs;
|
||||
m_dep_mgr.linearize(deps, vs);
|
||||
for (dep_source const& d : vs) {
|
||||
if (std::holds_alternative<enode_pair>(d))
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ bool theory_seq::len_based_split(depeq const& e) {
|
|||
expr_ref_vector const& rs = e.rs;
|
||||
|
||||
int offset = 0;
|
||||
if (!has_len_offset(ls, rs, offset))
|
||||
if (!has_len_offset(ls, rs, offset) || offset == 0)
|
||||
return false;
|
||||
|
||||
TRACE(seq, tout << "split based on length\n";);
|
||||
|
|
|
|||
|
|
@ -470,9 +470,10 @@ namespace smt {
|
|||
re_expr = m_seq.re.mk_inter(re_expr, loop);
|
||||
}
|
||||
|
||||
zstring str;
|
||||
expr_ref witness(m);
|
||||
// We checked non-emptiness during Nielsen already
|
||||
lbool wr = m_rewriter.some_seq_in_re(re_expr, witness);
|
||||
lbool wr = m_rewriter.some_string_in_re(re_expr, str);
|
||||
if (wr != l_true) {
|
||||
// some_seq_in_re can fail (l_undef / l_false) on regexes it does
|
||||
// not fully support — notably projection operators (re.proj),
|
||||
|
|
@ -482,7 +483,7 @@ namespace smt {
|
|||
wr = derivative_witness(m_sg.mk(re_expr), witness);
|
||||
}
|
||||
if (wr == l_true) {
|
||||
SASSERT(witness);
|
||||
witness = m_seq.str.mk_string(str);
|
||||
m_trail.push_back(witness);
|
||||
m_factory->register_value(witness);
|
||||
return witness;
|
||||
|
|
|
|||
|
|
@ -248,6 +248,17 @@ namespace smt {
|
|||
th.add_axiom(~lit);
|
||||
return true;
|
||||
}
|
||||
// Fall back to antimirov NFA reachability. The lazy state graph
|
||||
// keys states by AST identity and cannot close on intersections /
|
||||
// complements whose derivative product states do not canonicalize,
|
||||
// so it never detects their emptiness. re_is_empty decides
|
||||
// emptiness directly (the same procedure propagate_eq already uses
|
||||
// for re.none equalities).
|
||||
if (re_is_empty(r) == l_true) {
|
||||
STRACE(seq_regex_brief, tout << "(empty:re) ";);
|
||||
th.add_axiom(~lit);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -413,8 +424,15 @@ namespace smt {
|
|||
}
|
||||
|
||||
expr_ref seq_regex::symmetric_diff(expr* r1, expr* r2) {
|
||||
seq_rewriter rw(m);
|
||||
auto r = rw.mk_symmetric_diff(r1, r2);
|
||||
expr_ref r(m);
|
||||
if (r1 == r2)
|
||||
r = re().mk_empty(r1->get_sort());
|
||||
else if (re().is_empty(r1))
|
||||
r = r2;
|
||||
else if (re().is_empty(r2))
|
||||
r = r1;
|
||||
else
|
||||
r = re().mk_union(re().mk_diff(r1, r2), re().mk_diff(r2, r1));
|
||||
rewrite(r);
|
||||
return r;
|
||||
}
|
||||
|
|
@ -478,6 +496,24 @@ namespace smt {
|
|||
if (re().is_empty(r))
|
||||
//trivially true
|
||||
return;
|
||||
// When one side is re.none the equation is a pure emptiness check on
|
||||
// the other regex (symmetric_diff already returned it as r). Decide
|
||||
// it directly by antimirov NFA reachability instead of running the
|
||||
// bisimulation/XOR closure, which would build large un-canonicalized
|
||||
// product states for intersections of contains-patterns.
|
||||
if ((re().is_empty(r1) || re().is_empty(r2)) && is_ground(r)) {
|
||||
switch (re_is_empty(r)) {
|
||||
case l_true:
|
||||
STRACE(seq_regex_brief, tout << "empty:eq ";);
|
||||
return; // languages equal (both empty): trivially true
|
||||
case l_false:
|
||||
STRACE(seq_regex_brief, tout << "empty:neq ";);
|
||||
th.add_axiom(~th.mk_eq(r1, r2, false), false_literal);
|
||||
return;
|
||||
case l_undef:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Try the bisimulation procedure on ground regexes first. If it
|
||||
// returns a definite answer, dispatch the corresponding axiom and
|
||||
// bypass the symbolic emptiness/derivative closure.
|
||||
|
|
@ -579,16 +615,16 @@ namespace smt {
|
|||
lits.push_back(null_lit);
|
||||
|
||||
expr_ref_pair_vector cofactors(m);
|
||||
get_cofactors(d, cofactors);
|
||||
for (auto const& p : cofactors) {
|
||||
if (is_member(p.second, u))
|
||||
seq_rw().get_cofactors(hd, d, cofactors);
|
||||
for (auto const& [c, r] : cofactors) {
|
||||
if (is_member(r, u))
|
||||
continue;
|
||||
expr_ref cond(p.first, m);
|
||||
expr_ref cond(c, m);
|
||||
seq_rw().elim_condition(hd, cond);
|
||||
rewrite(cond);
|
||||
if (m.is_false(cond))
|
||||
continue;
|
||||
expr_ref next_non_empty = sk().mk_is_non_empty(p.second, re().mk_union(u, p.second), n);
|
||||
expr_ref next_non_empty = sk().mk_is_non_empty(r, re().mk_union(u, r), n);
|
||||
if (!m.is_true(cond))
|
||||
next_non_empty = m.mk_and(cond, next_non_empty);
|
||||
lits.push_back(th.mk_literal(next_non_empty));
|
||||
|
|
@ -689,40 +725,113 @@ namespace smt {
|
|||
}
|
||||
|
||||
/*
|
||||
Return a list of all target regexes in the derivative of a regex r,
|
||||
ignoring the conditions along each path.
|
||||
Decide emptiness of a ground regex r via antimirov-mode NFA
|
||||
reachability.
|
||||
|
||||
The derivative construction uses (:var 0) and tries
|
||||
to eliminate unsat condition paths but it does not perform
|
||||
full satisfiability checks and it is not guaranteed
|
||||
that all targets are actually reachable
|
||||
The symbolic derivative engine runs in antimirov mode, so the
|
||||
derivative of an intersection distributes into a *set* of individual
|
||||
product states inter(A_i, B_j) (each a small, ground regex) rather
|
||||
than one giant union-of-intersections term. get_derivative_targets
|
||||
enumerates these NFA successor states.
|
||||
|
||||
We short-circuit to l_false (non-empty) as soon as a reachable state
|
||||
is nullable (accepts the empty word) or classical (a regex built only
|
||||
from to_re/all/union/concat/star/plus/opt/loop, hence non-empty). An
|
||||
intersection itself is never classical, but once one operand reduces
|
||||
to Σ* the intersection collapses (via the derivative's subset
|
||||
simplification) to the other, classical, operand.
|
||||
|
||||
If the worklist is exhausted with no such state, r is empty (l_true).
|
||||
Returns l_undef if a step bound is hit, so callers can fall back to
|
||||
the general procedure.
|
||||
*/
|
||||
void seq_regex::get_derivative_targets(expr* r, expr_ref_vector& targets) {
|
||||
// constructs the derivative wrt (:var 0)
|
||||
expr_ref d(seq_rw().mk_derivative(r), m);
|
||||
|
||||
// use DFS to collect all the targets (leaf regexes) in d.
|
||||
expr* _1 = nullptr, * e1 = nullptr, * e2 = nullptr;
|
||||
obj_hashtable<expr>::entry* _2 = nullptr;
|
||||
vector<expr*> workset;
|
||||
workset.push_back(d);
|
||||
obj_hashtable<expr> done;
|
||||
done.insert(d);
|
||||
while (workset.size() > 0) {
|
||||
expr* e = workset.back();
|
||||
workset.pop_back();
|
||||
if (m.is_ite(e, _1, e1, e2) || re().is_union(e, e1, e2)) {
|
||||
if (done.insert_if_not_there_core(e1, _2))
|
||||
workset.push_back(e1);
|
||||
if (done.insert_if_not_there_core(e2, _2))
|
||||
workset.push_back(e2);
|
||||
lbool seq_regex::re_is_empty(expr* r) {
|
||||
if (re().is_empty(r))
|
||||
return l_true;
|
||||
expr_ref_vector pinned(m);
|
||||
obj_hashtable<expr> visited;
|
||||
ptr_vector<expr> work;
|
||||
work.push_back(r);
|
||||
visited.insert(r);
|
||||
pinned.push_back(r);
|
||||
unsigned const bound = 100000;
|
||||
unsigned steps = 0;
|
||||
while (!work.empty()) {
|
||||
if (++steps > bound)
|
||||
return l_undef;
|
||||
expr* s = work.back();
|
||||
work.pop_back();
|
||||
auto info = re().get_info(s);
|
||||
if (!info.is_known())
|
||||
return l_undef;
|
||||
// ε ∈ L(s) or s is a non-empty classical regex ⇒ L(r) non-empty.
|
||||
if (info.nullable == l_true || info.classical)
|
||||
return l_false;
|
||||
// Dead state: prune (min_length == UINT_MAX means no word is
|
||||
// accepted from here).
|
||||
if (info.min_length == UINT_MAX)
|
||||
continue;
|
||||
expr_ref_vector targets(m);
|
||||
get_derivative_targets(s, targets);
|
||||
for (expr* t : targets) {
|
||||
if (visited.contains(t))
|
||||
continue;
|
||||
visited.insert(t);
|
||||
pinned.push_back(t);
|
||||
work.push_back(t);
|
||||
}
|
||||
else if (!re().is_empty(e))
|
||||
targets.push_back(e);
|
||||
}
|
||||
return l_true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Return a list of all reachable target regexes in the derivative of a
|
||||
regex r.
|
||||
|
||||
The derivative is taken wrt (:var 0) and its reachable leaves are
|
||||
enumerated with the path-aware cofactor engine, which conjoins the
|
||||
ITE-path conditions and prunes infeasible character-range combinations
|
||||
(e.g. a nested branch requiring elem = 'a' and elem = 'B'). Each leaf
|
||||
is re-normalized with the path-aware smart constructors so that
|
||||
semantically equal states stay syntactically identical (essential for
|
||||
state dedup in the emptiness closure).
|
||||
|
||||
Without this pruning the naive ITE-tree DFS would reach infeasible
|
||||
leaves; an infeasible classical (intersection/complement-free) leaf
|
||||
would then be misjudged as a non-empty residual.
|
||||
*/
|
||||
void seq_regex::get_derivative_targets(expr* r, expr_ref_vector& targets) {
|
||||
expr_ref_pair_vector cofactors(m);
|
||||
seq_rw().brz_derivative_cofactors(r, cofactors);
|
||||
for (auto const& [c, t] : cofactors) {
|
||||
if (!re().is_empty(t))
|
||||
targets.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Return a list of all (cond, leaf) pairs in a given derivative
|
||||
expression r, where elem is the character symbol the derivative was
|
||||
taken with respect to.
|
||||
|
||||
The transition regexes produced by the symbolic derivative engine are
|
||||
ITE-trees over character predicates ci on elem (equalities such as
|
||||
elem = 'A', and ranges such as 'a' <= elem <= 'z'). These predicates
|
||||
are typically mutually exclusive, so the number of feasible truth
|
||||
assignments to {c1,..,ck} ("minterms") is small.
|
||||
|
||||
The enumeration is delegated to seq::derive (via seq_rw().get_cofactors)
|
||||
so it reuses the very same path/interval context that the derivative
|
||||
engine uses while hoisting ITEs: each feasible path through the ITE-tree
|
||||
yields one (path_condition, leaf) cofactor, infeasible character-range
|
||||
combinations are pruned, and the leaf is simplified with the path-aware
|
||||
smart constructors.
|
||||
|
||||
This is used by:
|
||||
propagate_is_empty
|
||||
propagate_is_non_empty
|
||||
*/
|
||||
|
||||
/*
|
||||
is_empty(r, u) => ~is_nullable(r)
|
||||
|
|
@ -750,11 +859,11 @@ namespace smt {
|
|||
d = mk_derivative_wrapper(hd, r);
|
||||
literal_vector lits;
|
||||
expr_ref_pair_vector cofactors(m);
|
||||
get_cofactors(d, cofactors);
|
||||
for (auto const& p : cofactors) {
|
||||
if (is_member(p.second, u))
|
||||
seq_rw().get_cofactors(hd, d, cofactors);
|
||||
for (auto const& [c, r] : cofactors) {
|
||||
if (is_member(r, u))
|
||||
continue;
|
||||
expr_ref cond(p.first, m);
|
||||
expr_ref cond(c, m);
|
||||
seq_rw().elim_condition(hd, cond);
|
||||
rewrite(cond);
|
||||
if (m.is_false(cond))
|
||||
|
|
@ -765,7 +874,7 @@ namespace smt {
|
|||
expr_ref ncond(mk_not(m, cond), m);
|
||||
lits.push_back(th.mk_literal(mk_forall(m, hd, ncond)));
|
||||
}
|
||||
expr_ref is_empty1 = sk().mk_is_empty(p.second, re().mk_union(u, p.second), n);
|
||||
expr_ref is_empty1 = sk().mk_is_empty(r, re().mk_union(u, r), n);
|
||||
lits.push_back(th.mk_literal(is_empty1));
|
||||
th.add_axiom(lits);
|
||||
}
|
||||
|
|
@ -878,50 +987,4 @@ namespace smt {
|
|||
return std::string("id") + std::to_string(e->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
Return a list of all (cond, leaf) pairs in a given
|
||||
expression r.
|
||||
|
||||
Note: this implementation is inefficient: it simply collects all expressions under an if and
|
||||
iterates over all combinations.
|
||||
*/
|
||||
void seq_regex::get_cofactors(expr *r, expr_ref_pair_vector &result) {
|
||||
obj_hashtable<expr> ifs;
|
||||
expr *cond = nullptr, *r1 = nullptr, *r2 = nullptr;
|
||||
for (expr *e : subterms::ground(expr_ref(r, m)))
|
||||
if (m.is_ite(e, cond, r1, r2))
|
||||
ifs.insert(cond);
|
||||
|
||||
expr_ref_vector rs(m);
|
||||
vector<expr_ref_vector> conds;
|
||||
conds.push_back(expr_ref_vector(m));
|
||||
rs.push_back(r);
|
||||
for (expr *c : ifs) {
|
||||
unsigned sz = conds.size();
|
||||
expr_safe_replace rep1(m);
|
||||
expr_safe_replace rep2(m);
|
||||
rep1.insert(c, m.mk_true());
|
||||
rep2.insert(c, m.mk_false());
|
||||
expr_ref r2(m);
|
||||
for (unsigned i = 0; i < sz; ++i) {
|
||||
expr_ref_vector cs = conds[i];
|
||||
cs.push_back(m.mk_not(c));
|
||||
conds.push_back(cs);
|
||||
conds[i].push_back(c);
|
||||
expr_ref r1(rs.get(i), m);
|
||||
rep1(r1, r2);
|
||||
rs[i] = r2;
|
||||
rep2(r1, r2);
|
||||
rs.push_back(r2);
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i < conds.size(); ++i) {
|
||||
expr_ref conj = mk_and(conds[i]);
|
||||
expr_ref r(rs.get(i), m);
|
||||
ctx.get_rewriter()(r);
|
||||
if (!m.is_false(conj) && !re().is_empty(r))
|
||||
result.push_back(conj, r);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,12 @@ namespace smt {
|
|||
expr_ref mk_deriv_accept(expr* s, unsigned i, expr* r);
|
||||
void get_derivative_targets(expr* r, expr_ref_vector& targets);
|
||||
|
||||
// Decide emptiness of a ground regex by antimirov-mode NFA
|
||||
// reachability: explore derivative target states, short-circuiting to
|
||||
// "non-empty" on the first reachable nullable or classical state.
|
||||
// Returns l_true (empty), l_false (non-empty), l_undef (gave up).
|
||||
lbool re_is_empty(expr* r);
|
||||
|
||||
/*
|
||||
Pretty print the regex of the state id to the out stream,
|
||||
seq_regex_ptr must be a pointer to seq_regex and the
|
||||
|
|
@ -183,9 +189,6 @@ namespace smt {
|
|||
|
||||
bool block_if_empty(expr* r, literal lit);
|
||||
|
||||
void get_cofactors(expr *r, expr_ref_pair_vector &result);
|
||||
|
||||
|
||||
public:
|
||||
|
||||
seq_regex(theory_seq& th);
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ namespace smt {
|
|||
if (!m_setup.already_configured()) {
|
||||
m_fparams.updt_params(p);
|
||||
}
|
||||
else {
|
||||
// selected parameters are safe to update after initialization
|
||||
m_fparams.m_max_conflicts = p.get_uint("max_conflicts", m_fparams.m_max_conflicts);
|
||||
}
|
||||
for (auto th : m_theory_set)
|
||||
if (th)
|
||||
th->updt_params();
|
||||
|
|
@ -3673,6 +3677,13 @@ namespace smt {
|
|||
}
|
||||
}
|
||||
|
||||
void context::setup_for_parallel() {
|
||||
// Native SMT parallel configures the parent context before cloning workers.
|
||||
// context::copy then configures/internalizes each worker copy while
|
||||
// preprocessing is still enabled.
|
||||
setup_context(m_fparams.m_auto_config);
|
||||
}
|
||||
|
||||
config_mode context::get_config_mode(bool use_static_features) const {
|
||||
if (!m_fparams.m_auto_config)
|
||||
return CFG_BASIC;
|
||||
|
|
@ -4693,7 +4704,6 @@ namespace smt {
|
|||
theory_id th_id = l->get_id();
|
||||
|
||||
for (enode * parent : enode::parents(n)) {
|
||||
auto p = parent->get_expr();
|
||||
family_id fid = parent->get_family_id();
|
||||
if (fid != th_id && fid != m.get_basic_family_id()) {
|
||||
if (is_beta_redex(parent, n))
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ namespace smt {
|
|||
|
||||
class model_generator;
|
||||
class context;
|
||||
class kernel;
|
||||
|
||||
struct oom_exception : public z3_error {
|
||||
oom_exception() : z3_error(ERR_MEMOUT) {}
|
||||
|
|
@ -85,6 +86,7 @@ namespace smt {
|
|||
friend class model_generator;
|
||||
friend class lookahead;
|
||||
friend class parallel;
|
||||
friend class kernel;
|
||||
public:
|
||||
statistics m_stats;
|
||||
|
||||
|
|
@ -294,6 +296,10 @@ namespace smt {
|
|||
return m_fparams;
|
||||
}
|
||||
|
||||
smt_params const& get_fparams() const {
|
||||
return m_fparams;
|
||||
}
|
||||
|
||||
params_ref const & get_params() {
|
||||
return m_params;
|
||||
}
|
||||
|
|
@ -454,6 +460,8 @@ namespace smt {
|
|||
svector<double> const & get_activity_vector() const { return m_activity; }
|
||||
|
||||
double get_activity(bool_var v) const { return m_activity[v]; }
|
||||
unsigned get_num_assignments() const { return m_stats.m_num_assignments; }
|
||||
unsigned get_birthdate(bool_var v) const { return m_birthdate[v]; }
|
||||
|
||||
void set_activity(bool_var v, double act) { m_activity[v] = act; }
|
||||
|
||||
|
|
@ -540,6 +548,8 @@ namespace smt {
|
|||
return m_scope_lvl == m_search_lvl;
|
||||
}
|
||||
|
||||
void pop_to_search_level() { pop_to_search_lvl(); }
|
||||
|
||||
bool tracking_assumptions() const {
|
||||
return !m_assumptions.empty() && m_search_lvl > m_base_lvl;
|
||||
}
|
||||
|
|
@ -872,7 +882,7 @@ namespace smt {
|
|||
void undo_mk_enode();
|
||||
|
||||
|
||||
void apply_sort_cnstr(app * term, enode * e);
|
||||
void apply_sort_cnstr(expr * term, enode * e);
|
||||
|
||||
bool simplify_aux_clause_literals(unsigned & num_lits, literal * lits, literal_buffer & simp_lits);
|
||||
|
||||
|
|
@ -1699,6 +1709,8 @@ namespace smt {
|
|||
|
||||
lbool setup_and_check(bool reset_cancel = true);
|
||||
|
||||
void setup_for_parallel();
|
||||
|
||||
void reduce_assertions();
|
||||
|
||||
bool resource_limits_exceeded();
|
||||
|
|
@ -1917,5 +1929,3 @@ namespace smt {
|
|||
std::ostream& operator<<(std::ostream& out, enode_pp const& p);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -597,9 +597,10 @@ namespace smt {
|
|||
SASSERT(is_lambda(q));
|
||||
if (e_internalized(q))
|
||||
return;
|
||||
mk_enode(q, true, /* do suppress args */
|
||||
auto e = mk_enode(q, true, /* do suppress args */
|
||||
false, /* it is a term, so it should not be merged with true/false */
|
||||
true);
|
||||
apply_sort_cnstr(q, e);
|
||||
}
|
||||
|
||||
bool context::has_lambda() {
|
||||
|
|
@ -1084,8 +1085,8 @@ namespace smt {
|
|||
/**
|
||||
\brief Apply sort constraints on e.
|
||||
*/
|
||||
void context::apply_sort_cnstr(app * term, enode * e) {
|
||||
sort * s = term->get_decl()->get_range();
|
||||
void context::apply_sort_cnstr(expr * term, enode * e) {
|
||||
sort * s = term->get_sort();
|
||||
theory * th = m_theories.get_plugin(s->get_family_id());
|
||||
if (th) {
|
||||
th->apply_sort_cnstr(e, s);
|
||||
|
|
|
|||
|
|
@ -284,10 +284,22 @@ namespace smt {
|
|||
smt_params_helper::collect_param_descrs(d);
|
||||
}
|
||||
|
||||
void kernel::pop_to_base_level() {
|
||||
m_imp->m_kernel.pop_to_base_lvl();
|
||||
}
|
||||
|
||||
void kernel::set_preprocess(bool f) {
|
||||
m_imp->m_kernel.get_fparams().m_preprocess = f;
|
||||
}
|
||||
|
||||
context & kernel::get_context() {
|
||||
return m_imp->m_kernel;
|
||||
}
|
||||
|
||||
context const& kernel::get_context() const {
|
||||
return m_imp->m_kernel;
|
||||
}
|
||||
|
||||
void kernel::get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) {
|
||||
m_imp->m_kernel.get_levels(vars, depth);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,6 +300,10 @@ namespace smt {
|
|||
*/
|
||||
static void collect_param_descrs(param_descrs & d);
|
||||
|
||||
void pop_to_base_level();
|
||||
|
||||
void set_preprocess(bool f);
|
||||
|
||||
void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause);
|
||||
|
||||
/**
|
||||
|
|
@ -340,6 +344,6 @@ namespace smt {
|
|||
\warning This method should not be used in new code.
|
||||
*/
|
||||
context & get_context();
|
||||
context const& get_context() const;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ namespace smt {
|
|||
|
||||
if (use_inv) {
|
||||
unsigned sk_term_gen = 0;
|
||||
expr * sk_term = m_model_finder.get_inv(q, i, sk_value, sk_term_gen);
|
||||
expr * sk_term = m_model_finder.get_inv(q, i, sk_value, *cex, sk_term_gen);
|
||||
if (sk_term != nullptr) {
|
||||
TRACE(model_checker, tout << "Found inverse " << mk_pp(sk_term, m) << "\n";);
|
||||
SASSERT(!m.is_model_value(sk_term));
|
||||
|
|
@ -233,15 +233,10 @@ namespace smt {
|
|||
}
|
||||
else {
|
||||
expr * sk_term = get_term_from_ctx(sk_value);
|
||||
func_decl * f = nullptr;
|
||||
if (sk_term != nullptr) {
|
||||
TRACE(model_checker, tout << "sk term " << mk_pp(sk_term, m) << "\n");
|
||||
sk_value = sk_term;
|
||||
}
|
||||
// last ditch: am I an array?
|
||||
else if (false && autil.is_as_array(sk_value, f) && cex->get_func_interp(f) && cex->get_func_interp(f)->get_array_interp(f)) {
|
||||
sk_value = cex->get_func_interp(f)->get_array_interp(f);
|
||||
}
|
||||
|
||||
}
|
||||
if (contains_model_value(sk_value)) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Revision History:
|
|||
--*/
|
||||
#include "util/backtrackable_set.h"
|
||||
#include "ast/ast_util.h"
|
||||
#include "ast/has_free_vars.h"
|
||||
#include "ast/macros/macro_util.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/bv_decl_plugin.h"
|
||||
|
|
@ -31,6 +32,7 @@ Revision History:
|
|||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/well_sorted.h"
|
||||
#include "ast/ast_smt2_pp.h"
|
||||
#include "ast/rewriter/term_enumeration.h"
|
||||
#include "model/model_pp.h"
|
||||
#include "model/model_macro_solver.h"
|
||||
#include "smt/smt_model_finder.h"
|
||||
|
|
@ -107,9 +109,15 @@ namespace smt {
|
|||
}
|
||||
}
|
||||
|
||||
expr* get_inv(expr* v) const {
|
||||
expr* get_inv(expr* v, model& mdl) const {
|
||||
expr* t = nullptr;
|
||||
m_inv.find(v, t);
|
||||
if (!t) {
|
||||
for (auto [k, term] : m_inv) {
|
||||
if (mdl.are_equal(k, v))
|
||||
return term;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
|
|
@ -120,14 +128,11 @@ namespace smt {
|
|||
}
|
||||
|
||||
void mk_inverse(evaluator& ev) {
|
||||
for (auto const& kv : m_elems) {
|
||||
expr* t = kv.m_key;
|
||||
for (auto const &[t, gen] : m_elems) {
|
||||
SASSERT(!contains_model_value(t));
|
||||
unsigned gen = kv.m_value;
|
||||
expr* t_val = ev.eval(t, true);
|
||||
if (!t_val) break;
|
||||
TRACE(model_finder, tout << mk_pp(t, m) << " " << mk_pp(t_val, m) << "\n";);
|
||||
|
||||
expr* old_t = nullptr;
|
||||
if (m_inv.find(t_val, old_t)) {
|
||||
unsigned old_t_gen = 0;
|
||||
|
|
@ -187,14 +192,14 @@ namespace smt {
|
|||
\brief Base class used to solve model construction constraints.
|
||||
*/
|
||||
class node {
|
||||
unsigned m_id;
|
||||
node* m_find{ nullptr };
|
||||
unsigned m_eqc_size{ 1 };
|
||||
unsigned m_id = 0;
|
||||
node* m_find = nullptr;
|
||||
unsigned m_eqc_size = 1;
|
||||
|
||||
sort* m_sort; // sort of the elements in the instantiation set.
|
||||
sort* m_sort = nullptr; // sort of the elements in the instantiation set.
|
||||
|
||||
bool m_mono_proj{ false }; // relevant for integers & reals & bit-vectors
|
||||
bool m_signed_proj{ false }; // relevant for bit-vectors.
|
||||
bool m_mono_proj = false; // relevant for integers & reals & bit-vectors
|
||||
bool m_signed_proj = false; // relevant for bit-vectors.
|
||||
ptr_vector<node> m_avoid_set;
|
||||
ptr_vector<expr> m_exceptions;
|
||||
|
||||
|
|
@ -291,7 +296,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
void insert(expr* n, unsigned generation) {
|
||||
if (is_ground(n))
|
||||
if (is_ground(n) || (has_quantifiers(n) && !has_free_vars(n))) // this is a closed term
|
||||
get_root()->m_set->insert(n, generation);
|
||||
}
|
||||
|
||||
|
|
@ -599,7 +604,10 @@ namespace smt {
|
|||
}
|
||||
else {
|
||||
r = tmp;
|
||||
TRACE(model_finder, tout << "eval\n" << mk_pp(n, m) << "\n----->\n" << mk_pp(r, m) << "\n";);
|
||||
TRACE(model_finder, tout << "eval-failed\n" << mk_pp(n, m) << "\n----->\n" << mk_pp(r, m) << "\n";);
|
||||
if (is_lambda(tmp)) {
|
||||
r = m.mk_fresh_const("lambda", tmp->get_sort());
|
||||
}
|
||||
}
|
||||
m_eval_cache[model_completion].insert(n, r);
|
||||
m_eval_cache_range.push_back(r);
|
||||
|
|
@ -1235,8 +1243,8 @@ namespace smt {
|
|||
void populate_inst_sets(quantifier* q, func_decl* mhead, ptr_vector<instantiation_set>& uvar_inst_sets, context* ctx) override {
|
||||
if (m_f != mhead)
|
||||
return;
|
||||
uvar_inst_sets.reserve(m_var_j + 1, 0);
|
||||
if (uvar_inst_sets[m_var_j] == 0)
|
||||
uvar_inst_sets.reserve(m_var_j + 1, nullptr);
|
||||
if (uvar_inst_sets[m_var_j] == nullptr)
|
||||
uvar_inst_sets[m_var_j] = alloc(instantiation_set, ctx->get_manager());
|
||||
instantiation_set* s = uvar_inst_sets[m_var_j];
|
||||
SASSERT(s != nullptr);
|
||||
|
|
@ -1369,6 +1377,81 @@ namespace smt {
|
|||
|
||||
};
|
||||
|
||||
class ho_var : public qinfo {
|
||||
unsigned m_var_i;
|
||||
public:
|
||||
ho_var(ast_manager& m, unsigned i) : qinfo(m), m_var_i(i) {
|
||||
}
|
||||
|
||||
char const *get_kind() const override {
|
||||
return "ho_var";
|
||||
}
|
||||
|
||||
bool is_equal(qinfo const *qi) const override {
|
||||
if (qi->get_kind() != get_kind())
|
||||
return false;
|
||||
ho_var const *other = static_cast<ho_var const *>(qi);
|
||||
return m_var_i == other->m_var_i;
|
||||
}
|
||||
|
||||
void display(std::ostream &out) const override {
|
||||
out << "(" << "ho-var: " << m_var_i << ")";
|
||||
}
|
||||
|
||||
void process_auf(quantifier *q, auf_solver &s, context *ctx) override {
|
||||
/* node * S_i = */ s.get_uvar(q, m_var_i);
|
||||
}
|
||||
|
||||
void populate_inst_sets(quantifier *q, auf_solver &s, context *ctx) override {
|
||||
node *S = s.get_uvar(q, m_var_i);
|
||||
sort *srt = S->get_sort();
|
||||
|
||||
IF_VERBOSE(3, verbose_stream() << "ho_var::populate_inst_sets: " << q->get_id() << " " << mk_pp(srt, m) << "\n";);
|
||||
term_enumeration tn(m);
|
||||
// Add ground terms of type S.
|
||||
// Add productions for functions in E-graph
|
||||
// add other possible relevant functions such as equality over srt, Boolean operators
|
||||
|
||||
ast_mark visited;
|
||||
tn.add_production(m.mk_true());
|
||||
tn.add_production(m.mk_false());
|
||||
for (enode *n : ctx->enodes()) {
|
||||
if (!ctx->is_relevant(n))
|
||||
continue;
|
||||
auto e = n->get_expr();
|
||||
if (srt == n->get_sort()) {
|
||||
TRACE(model_finder, tout << "inserting " << mk_pp(e, m) << " into inst set\n");
|
||||
S->insert(e, n->get_generation());
|
||||
}
|
||||
else if (is_app(e) && to_app(e)->get_decl()->is_skolem())
|
||||
;
|
||||
else if (is_uninterp_const(e)) {
|
||||
TRACE(model_finder, tout << "add production " << mk_pp(e, m) << "\n");
|
||||
tn.add_production(e);
|
||||
}
|
||||
else if (is_uninterp(e)) {
|
||||
auto f = to_app(e)->get_decl();
|
||||
if (visited.is_marked(f))
|
||||
continue;
|
||||
visited.mark(f, true);
|
||||
TRACE(model_finder, tout << "add function " << mk_pp(f, m) << "\n");
|
||||
tn.add_production(f);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned max_count = 20;
|
||||
for (auto t : tn.enum_terms(srt)) {
|
||||
if (max_count == 0)
|
||||
break;
|
||||
--max_count;
|
||||
unsigned generation = 0; // todo - inherited from sub-term of t?
|
||||
TRACE(model_finder, tout << "ho_var: adding term " << mk_ismt2_pp(t, m)
|
||||
<< " to instantiation set of S" << std::endl;);
|
||||
S->insert(t, generation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
\brief auf_arr is a term (pattern) of the form:
|
||||
|
|
@ -2105,7 +2188,10 @@ namespace smt {
|
|||
process_app(to_app(curr));
|
||||
}
|
||||
else if (is_var(curr)) {
|
||||
m_info->m_is_auf = false; // unexpected occurrence of variable.
|
||||
if (m_array_util.is_array(curr)) {
|
||||
insert_qinfo(alloc(ho_var, m, to_var(curr)->get_idx()));
|
||||
}
|
||||
m_info->m_is_auf = false;
|
||||
}
|
||||
else {
|
||||
SASSERT(is_lambda(curr));
|
||||
|
|
@ -2163,7 +2249,6 @@ namespace smt {
|
|||
}
|
||||
|
||||
SASSERT(is_quantifier(atom));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void process_literal(expr* atom, polarity pol) {
|
||||
|
|
@ -2203,9 +2288,15 @@ namespace smt {
|
|||
if (is_app(curr)) {
|
||||
if (to_app(curr)->get_family_id() == m.get_basic_family_id() && m.is_bool(curr)) {
|
||||
switch (static_cast<basic_op_kind>(to_app(curr)->get_decl_kind())) {
|
||||
case OP_IMPLIES:
|
||||
case OP_IMPLIES:
|
||||
process_literal(to_app(curr)->get_arg(0), neg(pol));
|
||||
process_literal(to_app(curr)->get_arg(1), pol);
|
||||
break;
|
||||
case OP_XOR:
|
||||
UNREACHABLE(); // simplifier eliminated ANDs, IMPLIEs, and XORs
|
||||
for (expr *arg : *to_app(curr)) {
|
||||
visit_formula(arg, pol);
|
||||
visit_formula(arg, neg(pol));
|
||||
}
|
||||
break;
|
||||
case OP_OR:
|
||||
case OP_AND:
|
||||
|
|
@ -2515,11 +2606,12 @@ namespace smt {
|
|||
|
||||
Store in generation the generation of the result
|
||||
*/
|
||||
expr* model_finder::get_inv(quantifier* q, unsigned i, expr* val, unsigned& generation) {
|
||||
expr* model_finder::get_inv(quantifier* q, unsigned i, expr* val, model& mdl,unsigned& generation) {
|
||||
instantiation_set const* s = get_uvar_inst_set(q, i);
|
||||
if (s == nullptr)
|
||||
return nullptr;
|
||||
expr* t = s->get_inv(val);
|
||||
|
||||
expr* t = s->get_inv(val, mdl);
|
||||
if (m_auf_solver->is_default_representative(t))
|
||||
return val;
|
||||
if (t != nullptr) {
|
||||
|
|
@ -2555,16 +2647,27 @@ namespace smt {
|
|||
obj_map<expr, expr*> const& inv = s->get_inv_map();
|
||||
if (inv.empty())
|
||||
continue; // nothing to do
|
||||
ptr_buffer<expr> eqs;
|
||||
for (auto const& [val, _] : inv) {
|
||||
if (val->get_sort() == sk->get_sort())
|
||||
eqs.push_back(m.mk_eq(sk, val));
|
||||
expr_ref_vector eqs(m), defs(m);
|
||||
|
||||
for (auto const& [val, term] : inv) {
|
||||
if (val->get_sort() == sk->get_sort()) {
|
||||
if (is_lambda(term)) {
|
||||
eqs.push_back(m.mk_eq(sk, val));
|
||||
defs.push_back(m.mk_eq(val, term));
|
||||
}
|
||||
else
|
||||
eqs.push_back(m.mk_eq(sk, val));
|
||||
}
|
||||
}
|
||||
if (!eqs.empty()) {
|
||||
expr_ref new_cnstr(m);
|
||||
new_cnstr = m.mk_or(eqs);
|
||||
TRACE(model_finder, tout << "assert_restriction:\n" << mk_pp(new_cnstr, m) << "\n";);
|
||||
aux_ctx->assert_expr(new_cnstr);
|
||||
for (auto def : defs) {
|
||||
TRACE(model_finder, tout << "assert_def:\n" << mk_pp(def, m) << "\n";);
|
||||
aux_ctx->assert_expr(def);
|
||||
}
|
||||
asserted_something = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ namespace smt {
|
|||
void fix_model(proto_model * m);
|
||||
|
||||
quantifier * get_flat_quantifier(quantifier * q);
|
||||
expr * get_inv(quantifier * q, unsigned i, expr * val, unsigned & generation);
|
||||
expr * get_inv(quantifier * q, unsigned i, expr * val, model& m, unsigned & generation);
|
||||
bool restrict_sks_to_inst_set(context * aux_ctx, quantifier * q, expr_ref_vector const & sks);
|
||||
|
||||
void restart_eh();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Author:
|
|||
#include "smt/smt_parallel.h"
|
||||
#include "smt/smt_lookahead.h"
|
||||
#include "solver/solver_preprocess.h"
|
||||
#include "params/smt_parallel_params.hpp"
|
||||
#include "solver/parallel_params.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
|
|
@ -550,7 +550,7 @@ namespace smt {
|
|||
if (m_ablate_backtracking) {
|
||||
// Ablation: for each target, pass the entire path from root to that node
|
||||
for (auto const& target : targets) {
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node, target.cancel_epoch))
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node))
|
||||
continue;
|
||||
|
||||
// Reconstruct the full path from root to this target node
|
||||
|
|
@ -626,7 +626,7 @@ namespace smt {
|
|||
ctx->set_logic(p.ctx.m_setup.get_logic());
|
||||
context::copy(p.ctx, *ctx, true);
|
||||
ctx->pop_to_base_lvl();
|
||||
ctx->get_fparams().m_preprocess = false;
|
||||
ctx->get_fparams().m_preprocess = false; // avoid preprocessing lemmas that are exchanged
|
||||
}
|
||||
|
||||
void parallel::core_minimizer_worker::cancel() {
|
||||
|
|
@ -763,26 +763,42 @@ namespace smt {
|
|||
if (m_config.m_global_backbones) {
|
||||
bb_candidates local_candidates = find_backbone_candidates();
|
||||
b.collect_backbone_candidates(m_l2g, local_candidates);
|
||||
if (!m.inc())
|
||||
bool lease_canceled = false;
|
||||
if (!b.checkpoint_worker(id, lease, lease_canceled))
|
||||
return;
|
||||
if (lease_canceled) {
|
||||
LOG_WORKER(1, " abandoning canceled lease\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
lbool r = check_cube(cube);
|
||||
|
||||
if (b.lease_canceled(lease)) {
|
||||
bool lease_canceled = false;
|
||||
if (!b.checkpoint_worker(id, lease, lease_canceled))
|
||||
return;
|
||||
if (lease_canceled) {
|
||||
LOG_WORKER(1, " abandoning canceled lease\n");
|
||||
lease = {};
|
||||
m.limit().dec_cancel();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m.inc())
|
||||
return;
|
||||
|
||||
switch (r) {
|
||||
case l_undef: {
|
||||
update_max_thread_conflicts();
|
||||
LOG_WORKER(1, " found undef cube\n");
|
||||
// Escalating the per-thread conflict budget and re-splitting the
|
||||
// cube only helps when the cube was abandoned because the per-cube
|
||||
// conflict limit was reached. For any other source of incompleteness
|
||||
// (an incomplete theory, quantifiers, lambdas, resource limits, ...)
|
||||
// the verdict cannot change, so re-checking the same cube would spin
|
||||
// forever and the run hangs to a wall-clock timeout. Record a sound
|
||||
// 'unknown' verdict and stop working this branch instead.
|
||||
std::string reason = ctx->last_failure_as_string();
|
||||
if (reason != "max-conflicts-reached") {
|
||||
LOG_WORKER(1, " undef cube not conflict-limited (" << reason << "); reporting unknown\n");
|
||||
b.set_unknown(reason);
|
||||
return;
|
||||
}
|
||||
update_max_thread_conflicts();
|
||||
if (m_config.m_max_cube_depth <= cube.size())
|
||||
goto check_cube_start;
|
||||
|
||||
|
|
@ -790,7 +806,6 @@ namespace smt {
|
|||
if (!atom)
|
||||
goto check_cube_start;
|
||||
b.try_split(m_l2g, id, lease, atom, m_config.m_threads_max_conflicts);
|
||||
lease = {};
|
||||
simplify();
|
||||
break;
|
||||
}
|
||||
|
|
@ -825,7 +840,6 @@ namespace smt {
|
|||
b.backtrack(m_l2g, id, core_to_use, lease);
|
||||
if (m_config.m_core_minimize)
|
||||
b.enqueue_core_minimization(m_l2g, source, unsat_core);
|
||||
lease = {};
|
||||
|
||||
if (m_config.m_share_conflicts)
|
||||
b.collect_clause(m_l2g, id, mk_not(mk_and(unsat_core)));
|
||||
|
|
@ -854,10 +868,10 @@ namespace smt {
|
|||
m_num_initial_atoms = ctx->get_num_bool_vars();
|
||||
ctx->get_fparams().m_preprocess = false; // avoid preprocessing lemmas that are exchanged
|
||||
|
||||
smt_parallel_params pp(p.ctx.m_params);
|
||||
m_config.m_inprocessing = pp.inprocessing();
|
||||
m_config.m_global_backbones = pp.num_global_bb_batch_threads() > 0 || pp.num_global_bb_fl_threads() > 0;
|
||||
m_config.m_local_backbones = pp.local_backbones();
|
||||
parallel_params pp(p.ctx.m_params);
|
||||
m_config.m_inprocessing = false;
|
||||
m_config.m_global_backbones = pp.num_bb_threads() > 0;
|
||||
m_config.m_local_backbones = false;
|
||||
m_config.m_core_minimize = pp.core_minimize();
|
||||
m_config.m_ablate_backtracking = pp.ablate_backtracking();
|
||||
|
||||
|
|
@ -887,9 +901,9 @@ namespace smt {
|
|||
ctx->pop_to_base_lvl();
|
||||
m_shared_units_prefix = ctx->assigned_literals().size();
|
||||
m_num_initial_atoms = ctx->get_num_bool_vars();
|
||||
ctx->get_fparams().m_preprocess = false; // avoid preprocessing lemmas that are exchanged
|
||||
|
||||
smt_parallel_params pp(p.ctx.m_params);
|
||||
m_use_failed_literal_test = pp.num_global_bb_fl_threads() > 0;
|
||||
m_use_failed_literal_test = false;
|
||||
}
|
||||
|
||||
parallel::bb_candidates parallel::worker::find_backbone_candidates(unsigned k) {
|
||||
|
|
@ -1105,14 +1119,48 @@ namespace smt {
|
|||
return r;
|
||||
}
|
||||
|
||||
void parallel::batch_manager::release_lease_unlocked(unsigned worker_id, node* n) {
|
||||
if (worker_id >= m_worker_leases.size())
|
||||
void parallel::batch_manager::set_canceled_unlocked() {
|
||||
if (m_state != state::is_running)
|
||||
return;
|
||||
auto &lease = m_worker_leases[worker_id];
|
||||
if (!lease.leased_node || lease.leased_node != n)
|
||||
cancel_background_threads();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::set_canceled() {
|
||||
std::scoped_lock lock(mux);
|
||||
set_canceled_unlocked();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::release_worker_lease_unlocked(unsigned worker_id, node_lease& lease) {
|
||||
if (worker_id >= m_worker_leases.size()) {
|
||||
lease = {};
|
||||
return;
|
||||
m_search_tree.dec_active_workers(lease.leased_node);
|
||||
}
|
||||
auto& stored_lease = m_worker_leases[worker_id];
|
||||
if (!stored_lease.leased_node || stored_lease.leased_node != lease.leased_node) {
|
||||
lease = {};
|
||||
return;
|
||||
}
|
||||
bool cancel_signaled = stored_lease.cancel_signaled;
|
||||
m_search_tree.dec_active_workers(stored_lease.leased_node);
|
||||
stored_lease = {};
|
||||
lease = {};
|
||||
if (cancel_signaled)
|
||||
p.m_workers[worker_id]->limit().dec_cancel();
|
||||
}
|
||||
|
||||
bool parallel::batch_manager::attempt_release_canceled_lease_unlocked(unsigned worker_id, node_lease& lease) {
|
||||
if (m_state != state::is_running || !lease.leased_node || worker_id >= m_worker_leases.size())
|
||||
return false;
|
||||
|
||||
auto& stored_lease = m_worker_leases[worker_id];
|
||||
if (stored_lease.leased_node != lease.leased_node)
|
||||
return false;
|
||||
|
||||
if (!m_search_tree.is_lease_canceled(stored_lease.leased_node))
|
||||
return false;
|
||||
|
||||
release_worker_lease_unlocked(worker_id, lease);
|
||||
return true;
|
||||
}
|
||||
|
||||
void parallel::batch_manager::cancel_closed_leases_unlocked(unsigned source_worker_id) {
|
||||
|
|
@ -1124,7 +1172,7 @@ namespace smt {
|
|||
|
||||
// only cancel workers that currently hold a lease, whose lease is canceled,
|
||||
// and haven't already been signaled (prevents multiple inc_cancel() for same lease)
|
||||
if (lease.leased_node && !lease.cancel_signaled && m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch)) {
|
||||
if (lease.leased_node && !lease.cancel_signaled && m_search_tree.is_lease_canceled(lease.leased_node)) {
|
||||
p.m_workers[worker_id]->cancel_lease();
|
||||
m_worker_leases[worker_id].cancel_signaled = true;
|
||||
}
|
||||
|
|
@ -1132,7 +1180,7 @@ namespace smt {
|
|||
}
|
||||
|
||||
void parallel::batch_manager::backtrack(ast_translation &l2g, unsigned worker_id, expr_ref_vector const &core,
|
||||
node_lease const &lease) {
|
||||
node_lease& lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
vector<cube_config::literal> g_core;
|
||||
for (auto c : core)
|
||||
|
|
@ -1277,7 +1325,7 @@ namespace smt {
|
|||
if (!g_core.empty()) {
|
||||
collect_matching_targets_unlocked(source, g_core[0].get(), g_core, targets);
|
||||
for (auto const& target : targets) {
|
||||
if (!m_search_tree.is_lease_canceled(target.leased_node, target.cancel_epoch))
|
||||
if (!m_search_tree.is_lease_canceled(target.leased_node))
|
||||
m_search_tree.backtrack(target.leased_node, g_core);
|
||||
}
|
||||
}
|
||||
|
|
@ -1331,7 +1379,7 @@ namespace smt {
|
|||
for (node* t : matches) {
|
||||
if (!t || t == source)
|
||||
continue;
|
||||
if (m_search_tree.is_lease_canceled(t, t->get_cancel_epoch()))
|
||||
if (m_search_tree.is_lease_canceled(t))
|
||||
continue;
|
||||
|
||||
// When source is provided, keep only external matches. Nodes in the
|
||||
|
|
@ -1358,12 +1406,12 @@ namespace smt {
|
|||
if (!is_highest_ancestor)
|
||||
continue;
|
||||
|
||||
targets.push_back({ t, t->get_cancel_epoch() });
|
||||
targets.push_back({t});
|
||||
}
|
||||
}
|
||||
|
||||
void parallel::batch_manager::backtrack_unlocked(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core,
|
||||
node_lease const* lease, vector<node_lease> const* targets) {
|
||||
node_lease* lease, vector<node_lease> const* targets) {
|
||||
if (m_state != state::is_running)
|
||||
return;
|
||||
|
||||
|
|
@ -1374,17 +1422,25 @@ namespace smt {
|
|||
SASSERT(lease != nullptr || targets != nullptr);
|
||||
bool did_backtrack = false;
|
||||
|
||||
if (lease && !m_search_tree.is_lease_canceled(lease->leased_node, lease->cancel_epoch)) {
|
||||
// we close/backtrack regardless of whether this lease is stale or not, as long as the lease isn't canceled
|
||||
// i.e. worker 1 splits this node, but then worker 2 determines UNSAT --> worker 2 is stale but we still close this node and backtrack
|
||||
did_backtrack = true;
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager backtracking.\n");
|
||||
release_lease_unlocked(worker_id, lease->leased_node);
|
||||
m_search_tree.backtrack(lease->leased_node, g_core);
|
||||
if (lease) {
|
||||
if (!m_search_tree.is_lease_canceled(lease->leased_node)) {
|
||||
// we close/backtrack regardless of whether this lease is stale or not, as long as the lease isn't canceled
|
||||
// i.e. worker 1 splits this node, but then worker 2 determines UNSAT --> worker 2 is stale but we still close this node and backtrack
|
||||
did_backtrack = true;
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager backtracking.\n");
|
||||
node* leased_node = lease->leased_node;
|
||||
release_worker_lease_unlocked(worker_id, *lease);
|
||||
m_search_tree.backtrack(leased_node, g_core);
|
||||
}
|
||||
else {
|
||||
// the lease was canceled by another worker. don't backtrack on this node with whatever new core we just found with this thread
|
||||
// however, we do proceed to external targets, since the new code may have exposed new external targets we can close/backtrack
|
||||
attempt_release_canceled_lease_unlocked(worker_id, *lease);
|
||||
}
|
||||
}
|
||||
if (targets) {
|
||||
for (auto const& target : *targets) {
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node, target.cancel_epoch))
|
||||
if (m_search_tree.is_lease_canceled(target.leased_node))
|
||||
continue;
|
||||
|
||||
did_backtrack = true;
|
||||
|
|
@ -1410,37 +1466,59 @@ namespace smt {
|
|||
}
|
||||
|
||||
void parallel::batch_manager::try_split(ast_translation &l2g, unsigned worker_id,
|
||||
node_lease const &lease, expr *atom, unsigned effort) {
|
||||
node_lease& lease, expr *atom, unsigned effort) {
|
||||
std::scoped_lock lock(mux);
|
||||
|
||||
if (m_state != state::is_running)
|
||||
return;
|
||||
|
||||
if (m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch))
|
||||
if (m_search_tree.is_lease_canceled(lease.leased_node)) {
|
||||
attempt_release_canceled_lease_unlocked(worker_id, lease);
|
||||
return;
|
||||
}
|
||||
|
||||
expr_ref lit(m), nlit(m);
|
||||
lit = l2g(atom);
|
||||
nlit = mk_not(m, lit);
|
||||
bool did_split = m_search_tree.try_split(lease.leased_node, lease.cancel_epoch, lit, nlit, effort);
|
||||
node* leased_node = lease.leased_node;
|
||||
VERIFY(!leased_node->path_contains_atom(lit));
|
||||
VERIFY(!leased_node->path_contains_atom(nlit));
|
||||
bool did_split = m_search_tree.try_split(leased_node, lit, nlit, effort);
|
||||
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
release_worker_lease_unlocked(worker_id, lease);
|
||||
|
||||
if (did_split) {
|
||||
++m_stats.m_num_cubes;
|
||||
m_stats.m_max_cube_depth = std::max(m_stats.m_max_cube_depth, lease.leased_node->depth() + 1);
|
||||
m_stats.m_max_cube_depth = std::max(m_stats.m_max_cube_depth, leased_node->depth() + 1);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager splitting on literal: " << mk_bounded_pp(lit, m, 3) << "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void parallel::batch_manager::release_lease(unsigned worker_id, node_lease const &lease) {
|
||||
bool parallel::batch_manager::checkpoint_worker(unsigned worker_id, node_lease& lease, bool& lease_canceled) {
|
||||
std::scoped_lock lock(mux);
|
||||
release_lease_unlocked(worker_id, lease.leased_node);
|
||||
lease_canceled = false;
|
||||
SASSERT(worker_id < p.m_workers.size());
|
||||
|
||||
if (attempt_release_canceled_lease_unlocked(worker_id, lease)) {
|
||||
lease_canceled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p.m_workers[worker_id]->limit().inc())
|
||||
return true;
|
||||
|
||||
if (attempt_release_canceled_lease_unlocked(worker_id, lease)) {
|
||||
lease_canceled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
set_canceled_unlocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parallel::batch_manager::lease_canceled(node_lease const &lease) {
|
||||
std::scoped_lock lock(mux);
|
||||
return m_state == state::is_running && m_search_tree.is_lease_canceled(lease.leased_node, lease.cancel_epoch);
|
||||
return m_state == state::is_running && m_search_tree.is_lease_canceled(lease.leased_node);
|
||||
}
|
||||
|
||||
void parallel::batch_manager::collect_clause(ast_translation &l2g, unsigned source_worker_id, expr *clause) {
|
||||
|
|
@ -1662,7 +1740,7 @@ namespace smt {
|
|||
void parallel::batch_manager::set_sat(ast_translation &l2g, model &m) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting SAT.\n");
|
||||
if (m_state != state::is_running)
|
||||
if (m_state != state::is_running && m_state != state::is_unknown)
|
||||
return;
|
||||
m_state = state::is_sat;
|
||||
p.ctx.set_model(m.translate(l2g));
|
||||
|
|
@ -1672,7 +1750,7 @@ namespace smt {
|
|||
void parallel::batch_manager::set_unsat(ast_translation &l2g, expr_ref_vector const &unsat_core) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting UNSAT.\n");
|
||||
if (m_state != state::is_running)
|
||||
if (m_state != state::is_running && m_state != state::is_unknown)
|
||||
return;
|
||||
m_state = state::is_unsat;
|
||||
|
||||
|
|
@ -1683,6 +1761,16 @@ namespace smt {
|
|||
cancel_background_threads();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::set_unknown(std::string const &reason) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting UNKNOWN: " << reason << ".\n");
|
||||
if (m_state != state::is_running)
|
||||
return; // a definitive sat/unsat verdict or exception already won.
|
||||
m_state = state::is_unknown;
|
||||
m_reason_unknown = reason;
|
||||
cancel_background_threads();
|
||||
}
|
||||
|
||||
void parallel::batch_manager::set_exception(unsigned error_code) {
|
||||
std::scoped_lock lock(mux);
|
||||
IF_VERBOSE(1, verbose_stream() << "Batch manager setting exception code: " << error_code << ".\n");
|
||||
|
|
@ -1714,6 +1802,8 @@ namespace smt {
|
|||
return l_false;
|
||||
case state::is_sat:
|
||||
return l_true;
|
||||
case state::is_unknown:
|
||||
return l_undef;
|
||||
case state::is_exception_msg:
|
||||
throw default_exception(m_exception_msg.c_str());
|
||||
case state::is_exception_code:
|
||||
|
|
@ -1745,7 +1835,6 @@ namespace smt {
|
|||
IF_VERBOSE(2, m_search_tree.display(verbose_stream()); verbose_stream() << "\n";);
|
||||
|
||||
lease.leased_node = t;
|
||||
lease.cancel_epoch = t->get_cancel_epoch();
|
||||
if (id >= m_worker_leases.size())
|
||||
m_worker_leases.resize(id + 1);
|
||||
m_worker_leases[id] = lease;
|
||||
|
|
@ -1779,8 +1868,9 @@ namespace smt {
|
|||
m_worker_leases.reset();
|
||||
m_worker_leases.resize(p.m_workers.size());
|
||||
|
||||
smt_parallel_params pp(p.ctx.m_params);
|
||||
parallel_params pp(p.ctx.m_params);
|
||||
m_ablate_backtracking = pp.ablate_backtracking();
|
||||
m_canceled = false;
|
||||
}
|
||||
|
||||
void parallel::batch_manager::collect_statistics(::statistics &st) const {
|
||||
|
|
@ -1794,20 +1884,26 @@ namespace smt {
|
|||
}
|
||||
|
||||
lbool parallel::operator()(expr_ref_vector const &asms) {
|
||||
smt_parallel_params pp(ctx.m_params);
|
||||
unsigned num_global_bb_batch_threads = pp.num_global_bb_batch_threads();
|
||||
if (num_global_bb_batch_threads > 2)
|
||||
throw default_exception("smt_parallel.num_global_bb_batch_threads must be 0, 1, or 2");
|
||||
unsigned num_workers = std::min((unsigned)std::thread::hardware_concurrency(), ctx.get_fparams().m_threads);
|
||||
unsigned num_sls_threads = (pp.sls() ? 1 : 0);
|
||||
parallel_params pp(ctx.m_params);
|
||||
unsigned num_global_bb_threads = pp.num_bb_threads();
|
||||
if (num_global_bb_threads > 2)
|
||||
throw default_exception("parallel.num_bb_threads must be 0, 1, or 2");
|
||||
unsigned total_threads = std::min((unsigned)std::thread::hardware_concurrency(), ctx.get_fparams().m_threads);
|
||||
unsigned num_workers = total_threads;
|
||||
unsigned num_sls_threads = 0;
|
||||
unsigned num_core_min_threads = (pp.core_minimize() ? 1 : 0);
|
||||
unsigned num_global_bb_fl_threads = pp.num_global_bb_fl_threads();
|
||||
if (num_global_bb_fl_threads > 2)
|
||||
throw default_exception("smt_parallel.num_global_bb_fl_threads must be 0, 1, or 2");
|
||||
if (num_global_bb_fl_threads > 0 && num_global_bb_batch_threads > 0)
|
||||
throw default_exception("smt_parallel.num_global_bb_fl_threads and smt_parallel.num_global_bb_batch_threads cannot both be enabled");
|
||||
unsigned num_global_bb_threads = num_global_bb_fl_threads > 0 ? num_global_bb_fl_threads : num_global_bb_batch_threads;
|
||||
unsigned total_threads = num_workers + num_sls_threads + num_core_min_threads + num_global_bb_threads;
|
||||
if (num_workers > 2 + num_core_min_threads)
|
||||
num_workers -= num_core_min_threads;
|
||||
else
|
||||
num_core_min_threads = 0;
|
||||
if (num_workers > 2 + num_global_bb_threads)
|
||||
num_workers -= num_global_bb_threads;
|
||||
else
|
||||
num_global_bb_threads = 0;
|
||||
if (num_workers > 2 + num_sls_threads)
|
||||
num_workers -= num_sls_threads;
|
||||
else
|
||||
num_sls_threads = 0;
|
||||
|
||||
IF_VERBOSE(1, verbose_stream() << "Parallel SMT with " << total_threads << " threads\n";);
|
||||
ast_manager &m = ctx.m;
|
||||
|
|
@ -1841,7 +1937,7 @@ namespace smt {
|
|||
m_sls_worker = alloc(sls_worker, *this);
|
||||
sl.push_child(&(m_sls_worker->limit()));
|
||||
}
|
||||
if (pp.core_minimize()) {
|
||||
if (num_core_min_threads == 1) {
|
||||
m_core_minimizer_worker = alloc(core_minimizer_worker, *this, asms);
|
||||
sl.push_child(&(m_core_minimizer_worker->limit()));
|
||||
}
|
||||
|
|
@ -1856,18 +1952,52 @@ namespace smt {
|
|||
<< m_global_backbones_workers.size() << " global backbone threads.\n";);
|
||||
|
||||
m_batch_manager.initialize(num_global_bb_threads);
|
||||
|
||||
auto safe_run = [&](auto&& run_fn, reslimit& lim) {
|
||||
try {
|
||||
run_fn();
|
||||
if (lim.is_canceled())
|
||||
m_batch_manager.set_canceled();
|
||||
} catch (z3_error &err) {
|
||||
IF_VERBOSE(0, verbose_stream() << "Exception in parallel solver: " << err.what() << "\n");
|
||||
if (!lim.is_canceled())
|
||||
m_batch_manager.set_exception(err.error_code());
|
||||
else
|
||||
m_batch_manager.set_canceled();
|
||||
} catch (z3_exception &ex) {
|
||||
IF_VERBOSE(0, verbose_stream() << "Exception in parallel solver: " << ex.what() << "\n");
|
||||
if (!lim.is_canceled() && !is_cancellation_exception(ex.what()))
|
||||
m_batch_manager.set_exception(ex.what());
|
||||
else
|
||||
m_batch_manager.set_canceled();
|
||||
} catch (...) {
|
||||
IF_VERBOSE(0, verbose_stream() << "Unknown exception in parallel solver\n");
|
||||
if (!lim.is_canceled())
|
||||
m_batch_manager.set_exception("unknown exception");
|
||||
else
|
||||
m_batch_manager.set_canceled();
|
||||
}
|
||||
};
|
||||
|
||||
// Launch threads
|
||||
vector<std::thread> threads(total_threads);
|
||||
unsigned thread_idx = 0;
|
||||
for (auto* w : m_workers)
|
||||
threads[thread_idx++] = std::thread([&, w]() { w->run(); });
|
||||
threads[thread_idx++] = std::thread([w, &safe_run]() {
|
||||
safe_run([w]() { w->run(); }, w->limit());
|
||||
});
|
||||
if (m_sls_worker)
|
||||
threads[thread_idx++] = std::thread([&]() { m_sls_worker->run(); });
|
||||
threads[thread_idx++] = std::thread([this, &safe_run]() {
|
||||
safe_run([this]() { m_sls_worker->run(); }, m_sls_worker->limit());
|
||||
});
|
||||
if (m_core_minimizer_worker)
|
||||
threads[thread_idx++] = std::thread([&]() { m_core_minimizer_worker->run(); });
|
||||
threads[thread_idx++] = std::thread([this, &safe_run]() {
|
||||
safe_run([this]() { m_core_minimizer_worker->run(); }, m_core_minimizer_worker->limit());
|
||||
});
|
||||
for (auto* w : m_global_backbones_workers)
|
||||
threads[thread_idx++] = std::thread([&, w]() { w->run(); });
|
||||
threads[thread_idx++] = std::thread([w, &safe_run]() {
|
||||
safe_run([w]() { w->run(); }, w->limit());
|
||||
});
|
||||
|
||||
|
||||
// Wait for all threads to finish
|
||||
|
|
@ -1884,7 +2014,10 @@ namespace smt {
|
|||
for (auto* bb_w : m_global_backbones_workers)
|
||||
bb_w->collect_statistics(ctx.m_aux_stats);
|
||||
|
||||
return m_batch_manager.get_result();
|
||||
lbool result = m_batch_manager.get_result();
|
||||
if (result == l_undef && !m_batch_manager.get_reason_unknown().empty())
|
||||
ctx.set_reason_unknown(m_batch_manager.get_reason_unknown().c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace smt
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ namespace smt {
|
|||
struct cube_config {
|
||||
using literal = expr_ref;
|
||||
static bool literal_is_null(expr_ref const& l) { return l == nullptr; }
|
||||
static bool same_atom(expr_ref const& a, expr_ref const& b) {
|
||||
expr* atom_a = a.get();
|
||||
expr* atom_b = b.get();
|
||||
a.get_manager().is_not(atom_a, atom_a);
|
||||
b.get_manager().is_not(atom_b, atom_b);
|
||||
return atom_a == atom_b;
|
||||
}
|
||||
static std::ostream& display_literal(std::ostream& out, expr_ref const& l) { return out << mk_bounded_pp(l, l.get_manager()); }
|
||||
};
|
||||
|
||||
|
|
@ -74,6 +81,7 @@ namespace smt {
|
|||
is_running,
|
||||
is_sat,
|
||||
is_unsat,
|
||||
is_unknown,
|
||||
is_exception_msg,
|
||||
is_exception_code
|
||||
};
|
||||
|
|
@ -102,6 +110,7 @@ namespace smt {
|
|||
|
||||
unsigned m_exception_code = 0;
|
||||
std::string m_exception_msg;
|
||||
std::string m_reason_unknown;
|
||||
vector<shared_clause> shared_clause_trail; // store all shared clauses with worker IDs
|
||||
obj_hashtable<expr> shared_clause_set; // for duplicate filtering on per-thread clause expressions
|
||||
|
||||
|
|
@ -145,7 +154,11 @@ namespace smt {
|
|||
w->cancel();
|
||||
}
|
||||
|
||||
std::atomic<bool> m_canceled = false;
|
||||
|
||||
void cancel_background_threads() {
|
||||
if (m_canceled.exchange(true))
|
||||
return; // already canceled
|
||||
cancel_workers();
|
||||
cancel_sls_worker();
|
||||
if (!p.m_global_backbones_workers.empty()) {
|
||||
|
|
@ -171,9 +184,11 @@ namespace smt {
|
|||
}
|
||||
|
||||
void backtrack_unlocked(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core,
|
||||
node_lease const* lease = nullptr, vector<node_lease> const* targets = nullptr);
|
||||
node_lease* lease = nullptr, vector<node_lease> const* targets = nullptr);
|
||||
void collect_clause_unlocked(ast_translation &l2g, unsigned source_worker_id, expr *clause);
|
||||
void release_lease_unlocked(unsigned worker_id, node* n);
|
||||
void set_canceled_unlocked();
|
||||
void release_worker_lease_unlocked(unsigned worker_id, node_lease& lease);
|
||||
bool attempt_release_canceled_lease_unlocked(unsigned worker_id, node_lease& lease);
|
||||
void cancel_closed_leases_unlocked(unsigned source_worker_id);
|
||||
void collect_matching_targets_unlocked(node* source, expr* lit, vector<cube_config::literal> const& core,
|
||||
vector<node_lease>& targets);
|
||||
|
|
@ -187,6 +202,8 @@ namespace smt {
|
|||
|
||||
void set_unsat(ast_translation& l2g, expr_ref_vector const& unsat_core);
|
||||
void set_sat(ast_translation& l2g, model& m);
|
||||
void set_unknown(std::string const& reason);
|
||||
void set_canceled();
|
||||
void set_exception(std::string const& msg);
|
||||
void set_exception(unsigned error_code);
|
||||
void collect_statistics(::statistics& st) const;
|
||||
|
|
@ -210,20 +227,21 @@ namespace smt {
|
|||
}
|
||||
|
||||
bool get_cube(ast_translation& g2l, unsigned id, expr_ref_vector& cube, bool is_first_run, node_lease& lease);
|
||||
void backtrack(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core, node_lease const& lease);
|
||||
void backtrack(ast_translation& l2g, unsigned worker_id, expr_ref_vector const& core, node_lease& lease);
|
||||
void enqueue_core_minimization(ast_translation& l2g, node* source, expr_ref_vector const& core);
|
||||
bool wait_for_core_min_job(ast_translation& g2l, node*& source,
|
||||
expr_ref_vector& core, reslimit& lim);
|
||||
void publish_minimized_core(ast_translation& l2g, expr_ref_vector const& asms, node* source,
|
||||
unsigned original_core_size, expr_ref_vector const& minimized_core);
|
||||
void try_split(ast_translation& l2g, unsigned worker_id, node_lease const& lease, expr* atom, unsigned effort);
|
||||
void release_lease(unsigned worker_id, node_lease const& lease);
|
||||
void try_split(ast_translation& l2g, unsigned worker_id, node_lease& lease, expr* atom, unsigned effort);
|
||||
bool checkpoint_worker(unsigned worker_id, node_lease& lease, bool& lease_canceled);
|
||||
bool lease_canceled(node_lease const& lease);
|
||||
|
||||
void collect_clause(ast_translation& l2g, unsigned source_worker_id, expr* clause);
|
||||
expr_ref_vector return_shared_clauses(ast_translation& g2l, unsigned& worker_limit, unsigned worker_id);
|
||||
|
||||
lbool get_result() const;
|
||||
std::string const& get_reason_unknown() const { return m_reason_unknown; }
|
||||
|
||||
bool is_global_backbone_or_negation(ast_translation& l2g, expr* bb_cand) {
|
||||
std::scoped_lock lock(mux);
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@ Notes:
|
|||
#include "ast/for_each_expr.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/func_decl_dependencies.h"
|
||||
#include "smt/smt_context.h"
|
||||
#include "smt/smt_kernel.h"
|
||||
#include "params/smt_params.h"
|
||||
#include "params/smt_params_helper.hpp"
|
||||
#include "solver/solver_na2as.h"
|
||||
#include "solver/mus.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
class smt_solver : public solver_na2as {
|
||||
|
|
@ -61,6 +64,7 @@ namespace {
|
|||
smt_params m_smt_params;
|
||||
smt::kernel m_context;
|
||||
cuber* m_cuber;
|
||||
random_gen m_rand;
|
||||
symbol m_logic;
|
||||
bool m_minimizing_core;
|
||||
bool m_core_extend_patterns;
|
||||
|
|
@ -84,16 +88,19 @@ namespace {
|
|||
updt_params(p);
|
||||
}
|
||||
|
||||
solver * translate(ast_manager & m, params_ref const & p) override {
|
||||
ast_translation translator(get_manager(), m);
|
||||
solver * translate(ast_manager & target, params_ref const & p) override {
|
||||
ast_translation translator(get_manager(), target);
|
||||
params_ref init;
|
||||
init.copy(get_params());
|
||||
init.copy(p);
|
||||
|
||||
smt_solver * result = alloc(smt_solver, m, p, m_logic);
|
||||
smt_solver* result = alloc(smt_solver, target, init, m_logic);
|
||||
smt::kernel::copy(m_context, result->m_context, true);
|
||||
|
||||
if (mc0())
|
||||
if (mc0())
|
||||
result->set_model_converter(mc0()->translate(translator));
|
||||
|
||||
for (auto & [k, v] : m_name2assertion) {
|
||||
for (auto& [k, v] : m_name2assertion) {
|
||||
expr* val = translator(k);
|
||||
expr* key = translator(v);
|
||||
result->assert_expr(val, key);
|
||||
|
|
@ -212,6 +219,97 @@ namespace {
|
|||
return m_context.get_trail(max_level);
|
||||
}
|
||||
|
||||
expr_ref_vector get_assigned_literals() override {
|
||||
expr_ref_vector result(m);
|
||||
auto const& ctx = m_context.get_context();
|
||||
for (auto lit : ctx.assigned_literals()) {
|
||||
expr* atom = ctx.bool_var2expr(lit.var());
|
||||
if (!atom)
|
||||
continue;
|
||||
result.push_back(lit.sign() ? m.mk_not(atom) : atom);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned get_assign_level(expr* e) const override {
|
||||
auto const& ctx = m_context.get_context();
|
||||
get_manager().is_not(e, e);
|
||||
if (!ctx.b_internalized(e))
|
||||
return UINT_MAX;
|
||||
return ctx.get_assign_level(ctx.get_bool_var(e));
|
||||
}
|
||||
|
||||
bool is_relevant(expr* e) const override {
|
||||
auto const& ctx = m_context.get_context();
|
||||
get_manager().is_not(e, e);
|
||||
return ctx.b_internalized(e) && ctx.is_relevant(e);
|
||||
}
|
||||
|
||||
unsigned get_num_bool_vars() const override {
|
||||
return m_context.get_context().get_num_bool_vars();
|
||||
}
|
||||
|
||||
sat::bool_var get_bool_var(expr* e) const override {
|
||||
auto const& ctx = m_context.get_context();
|
||||
get_manager().is_not(e, e);
|
||||
return ctx.b_internalized(e) ? ctx.get_bool_var(e) : sat::null_bool_var;
|
||||
}
|
||||
|
||||
void pop_to_base_level() override {
|
||||
m_context.pop_to_base_level();
|
||||
}
|
||||
|
||||
void setup_for_parallel() override {
|
||||
m_context.get_context().setup_for_parallel();
|
||||
}
|
||||
|
||||
void set_preprocess(bool f) override {
|
||||
m_context.set_preprocess(f);
|
||||
}
|
||||
|
||||
void set_max_conflicts(unsigned max_conflicts) override {
|
||||
auto& ctx = m_context.get_context();
|
||||
ctx.get_fparams().m_max_conflicts = max_conflicts;
|
||||
}
|
||||
|
||||
unsigned get_max_conflicts() const override {
|
||||
return m_context.get_context().get_fparams().m_max_conflicts;
|
||||
}
|
||||
|
||||
void get_backbone_candidates(vector<solver::scored_literal>& candidates, unsigned max_num) override {
|
||||
ast_manager& m = get_manager();
|
||||
auto& ctx = m_context.get_context();
|
||||
unsigned curr_time = ctx.get_num_assignments();
|
||||
vector<solver::scored_literal> all;
|
||||
|
||||
for (unsigned v = 0; v < ctx.get_num_bool_vars(); ++v) {
|
||||
if (ctx.get_assignment(v) != l_undef && ctx.get_assign_level(v) == ctx.get_base_level())
|
||||
continue;
|
||||
|
||||
expr* candidate = ctx.bool_var2expr(v);
|
||||
if (!candidate)
|
||||
continue;
|
||||
|
||||
auto const& d = ctx.get_bdata(v);
|
||||
if (d.m_phase_available && !d.m_phase)
|
||||
candidate = m.mk_not(candidate);
|
||||
|
||||
double age = static_cast<double>(curr_time - ctx.get_birthdate(v));
|
||||
all.push_back(solver::scored_literal(m, candidate, age));
|
||||
}
|
||||
|
||||
std::stable_sort(
|
||||
all.begin(),
|
||||
all.end(),
|
||||
[](solver::scored_literal const& a, solver::scored_literal const& b) {
|
||||
return a.score > b.score;
|
||||
});
|
||||
|
||||
unsigned n = std::min<unsigned>(max_num, all.size());
|
||||
for (unsigned i = 0; i < n; ++i)
|
||||
candidates.push_back(all[i]);
|
||||
}
|
||||
|
||||
void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause) override {
|
||||
m_context.register_on_clause(ctx, on_clause);
|
||||
}
|
||||
|
|
@ -368,6 +466,39 @@ namespace {
|
|||
return lits;
|
||||
}
|
||||
|
||||
expr_ref cube_vsids(expr_ref_vector const& invalid_split_atoms) override {
|
||||
ast_manager& m = get_manager();
|
||||
auto& ctx = m_context.get_context();
|
||||
obj_hashtable<expr> invalid_split_atoms_set;
|
||||
for (expr* e : invalid_split_atoms) {
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
invalid_split_atoms_set.insert(atom);
|
||||
}
|
||||
expr_ref result(m);
|
||||
double score = 0.0;
|
||||
unsigned n = 0;
|
||||
|
||||
ctx.pop_to_search_level();
|
||||
for (unsigned v = 0; v < ctx.get_num_bool_vars(); ++v) {
|
||||
if (ctx.get_assignment(v) != l_undef)
|
||||
continue;
|
||||
expr* e = ctx.bool_var2expr(v);
|
||||
if (!e)
|
||||
continue;
|
||||
expr* atom = e;
|
||||
m.is_not(e, atom);
|
||||
if (invalid_split_atoms_set.contains(atom))
|
||||
continue;
|
||||
double new_score = ctx.get_activity(v);
|
||||
if (new_score > score || !result || (new_score == score && m_rand(++n) == 0)) {
|
||||
score = new_score;
|
||||
result = e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct collect_fds_proc {
|
||||
ast_manager & m;
|
||||
func_decl_set & m_fds;
|
||||
|
|
@ -537,4 +668,3 @@ public:
|
|||
solver_factory * mk_smt_solver_factory() {
|
||||
return alloc(smt_solver_factory);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ Notes:
|
|||
#include "solver/solver.h"
|
||||
#include "solver/mus.h"
|
||||
#include "solver/parallel_tactical.h"
|
||||
#include "solver/parallel_tactical2.h"
|
||||
#include "solver/parallel_params.hpp"
|
||||
#include <mutex>
|
||||
|
||||
|
|
@ -431,8 +430,6 @@ static tactic * mk_seq_smt_tactic(ast_manager& m, params_ref const & p) {
|
|||
|
||||
tactic * mk_parallel_smt_tactic(ast_manager& m, params_ref const& p) {
|
||||
parallel_params pp(p);
|
||||
if (pp.enable2())
|
||||
return mk_parallel_tactic2(mk_smt_solver(m, p, symbol::null), p);
|
||||
return mk_parallel_tactic(mk_smt_solver(m, p, symbol::null), p);
|
||||
}
|
||||
|
||||
|
|
@ -440,8 +437,6 @@ tactic * mk_smt_tactic_core(ast_manager& m, params_ref const& p, symbol const& l
|
|||
parallel_params pp(p);
|
||||
if (pp.enable())
|
||||
return mk_parallel_tactic(mk_smt_solver(m, p, logic), p);
|
||||
if (pp.enable2())
|
||||
return mk_parallel_tactic2(mk_smt_solver(m, p, logic), p);
|
||||
return mk_seq_smt_tactic(m, p);
|
||||
}
|
||||
|
||||
|
|
@ -450,7 +445,7 @@ tactic * mk_smt_tactic_core_using(ast_manager& m, bool auto_config, params_ref c
|
|||
params_ref p = _p;
|
||||
p.set_bool("auto_config", auto_config);
|
||||
tactic *t = nullptr;
|
||||
if (pp.enable() || pp.enable2())
|
||||
if (pp.enable())
|
||||
t = mk_parallel_smt_tactic(m, p);
|
||||
else
|
||||
t = mk_seq_smt_tactic(m, p);
|
||||
|
|
|
|||
|
|
@ -396,15 +396,15 @@ namespace smt {
|
|||
}
|
||||
|
||||
final_check_status theory_array::assert_delayed_axioms() {
|
||||
if (!m_params.m_array_delay_exp_axiom)
|
||||
return FC_DONE;
|
||||
final_check_status r = FC_DONE;
|
||||
unsigned num_vars = get_num_vars();
|
||||
for (unsigned v = 0; v < num_vars; ++v) {
|
||||
var_data * d = m_var_data[v];
|
||||
if (d->m_prop_upward && instantiate_axiom2b_for(v))
|
||||
r = FC_CONTINUE;
|
||||
}
|
||||
if (m_params.m_array_delay_exp_axiom) {
|
||||
unsigned num_vars = get_num_vars();
|
||||
for (unsigned v = 0; v < num_vars; ++v) {
|
||||
var_data *d = m_var_data[v];
|
||||
if (d->m_prop_upward && instantiate_axiom2b_for(v))
|
||||
r = FC_CONTINUE;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace smt {
|
|||
unsigned m_num_map_axiom, m_num_default_map_axiom;
|
||||
unsigned m_num_select_const_axiom, m_num_default_store_axiom, m_num_default_const_axiom, m_num_default_as_array_axiom;
|
||||
unsigned m_num_select_as_array_axiom, m_num_default_lambda_axiom, m_num_choice_axiom;
|
||||
unsigned m_num_select_lambda_axiom;
|
||||
void reset() { memset(this, 0, sizeof(theory_array_stats)); }
|
||||
theory_array_stats() { reset(); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ namespace smt {
|
|||
return mk_select(num_args, args);
|
||||
}
|
||||
|
||||
|
||||
app * theory_array_base::mk_store(unsigned num_args, expr * const * args) {
|
||||
return m.mk_app(get_family_id(), OP_STORE, 0, nullptr, num_args, args);
|
||||
}
|
||||
|
|
@ -279,7 +278,7 @@ namespace smt {
|
|||
SASSERT(n1->get_num_args() == n2->get_num_args());
|
||||
unsigned n = n1->get_num_args();
|
||||
// skipping first argument of the select.
|
||||
for(unsigned i = 1; i < n; ++i) {
|
||||
for (unsigned i = 1; i < n; ++i) {
|
||||
if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -295,9 +294,8 @@ namespace smt {
|
|||
enode * r1 = v1->get_root();
|
||||
enode * r2 = v2->get_root();
|
||||
|
||||
if (r1->get_class_size() > r2->get_class_size()) {
|
||||
std::swap(r1, r2);
|
||||
}
|
||||
if (r1->get_class_size() > r2->get_class_size())
|
||||
std::swap(r1, r2);
|
||||
|
||||
m_array_value.reset();
|
||||
// populate m_array_value if the select(a, i) parent terms of r1
|
||||
|
|
@ -335,7 +333,7 @@ namespace smt {
|
|||
return false; // axiom was already instantiated
|
||||
if (already_diseq(n1, n2))
|
||||
return false;
|
||||
m_extensionality_todo.push_back(std::make_pair(n1, n2));
|
||||
m_extensionality_todo.push_back({n1, n2});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +346,7 @@ namespace smt {
|
|||
enode * nodes[2] = { a1, a2 };
|
||||
if (!ctx.add_fingerprint(this, 1, 2, nodes))
|
||||
return; // axiom was already instantiated
|
||||
m_congruent_todo.push_back(std::make_pair(a1, a2));
|
||||
m_congruent_todo.push_back({a1, a2});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -536,6 +534,7 @@ namespace smt {
|
|||
unsigned num_vars = get_num_vars();
|
||||
for (unsigned i = 0; i < num_vars; ++i) {
|
||||
enode * n = get_enode(i);
|
||||
TRACE(array, tout << enode_pp(n, ctx) << " is_relevant: " << ctx.is_relevant(n) << " is_array: " << is_array_sort(n) << "\n";);
|
||||
if (!ctx.is_relevant(n) || !is_array_sort(n)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -581,11 +580,12 @@ namespace smt {
|
|||
enode * n2 = get_enode(v2);
|
||||
sort * s2 = n2->get_sort();
|
||||
if (s1 == s2 && !ctx.is_diseq(n1, n2)) {
|
||||
app * eq = mk_eq_atom(n1->get_expr(), n2->get_expr());
|
||||
if (!ctx.b_internalized(eq) || !ctx.is_relevant(eq)) {
|
||||
app_ref eq = app_ref(mk_eq_atom(n1->get_expr(), n2->get_expr()), m);
|
||||
TRACE(array_bug, tout << "mk_interface_eqs: adding: " << eq << "\n";);
|
||||
if (!ctx.b_internalized(eq.get()) || !ctx.is_relevant(eq.get())) {
|
||||
result++;
|
||||
ctx.internalize(eq, true);
|
||||
ctx.mark_as_relevant(eq);
|
||||
ctx.mark_as_relevant(eq.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -850,7 +850,7 @@ namespace smt {
|
|||
if (i < num_args) {
|
||||
SASSERT(!parent_sel_set->contains(sel) || (*(parent_sel_set->find(sel)))->get_root() == sel->get_root());
|
||||
parent_sel_set->insert(sel);
|
||||
todo.push_back(std::make_pair(parent_root, sel));
|
||||
todo.push_back({parent_root, sel});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,6 +393,10 @@ namespace smt {
|
|||
SASSERT(is_map(map));
|
||||
instantiate_select_map_axiom(s, map);
|
||||
}
|
||||
for (enode *lam : d_full->m_lambdas) {
|
||||
SASSERT(is_lambda(lam->get_expr()));
|
||||
instantiate_select_lambda_axiom(s, lam);
|
||||
}
|
||||
if (!m_params.m_array_delay_exp_axiom && d->m_prop_upward) {
|
||||
for (enode * map : d_full->m_parent_maps) {
|
||||
SASSERT(is_map(map));
|
||||
|
|
@ -468,7 +472,6 @@ namespace smt {
|
|||
SASSERT(map->get_num_args() > 0);
|
||||
func_decl* f = to_func_decl(map->get_decl()->get_parameter(0).get_ast());
|
||||
|
||||
|
||||
TRACE(array_map_bug, tout << "invoked instantiate_select_map_axiom\n";
|
||||
tout << sl->get_owner_id() << " " << mp->get_owner_id() << "\n";
|
||||
tout << mk_ismt2_pp(sl->get_expr(), m) << "\n" << mk_ismt2_pp(mp->get_expr(), m) << "\n";);
|
||||
|
|
@ -518,6 +521,34 @@ namespace smt {
|
|||
return try_assign_eq(sel1, sel2);
|
||||
}
|
||||
|
||||
bool theory_array_full::instantiate_select_lambda_axiom(enode* sl, enode* lambda) {
|
||||
app* select = sl->get_app();
|
||||
SASSERT(is_select(select));
|
||||
SASSERT(is_lambda(lambda->get_expr()));
|
||||
SASSERT(lambda->get_sort() == sl->get_arg(0)->get_sort());
|
||||
|
||||
if (!ctx.add_fingerprint(lambda, lambda->get_owner_id(), sl->get_num_args() - 1, sl->get_args() + 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stats.m_num_select_lambda_axiom++;
|
||||
|
||||
unsigned num_args = select->get_num_args();
|
||||
ptr_buffer<expr> args;
|
||||
args.push_back(lambda->get_expr());
|
||||
for (unsigned i = 1; i < num_args; ++i)
|
||||
args.push_back(select->get_arg(i));
|
||||
|
||||
expr_ref sel1(m), sel2(m);
|
||||
sel1 = mk_select(args.size(), args.data());
|
||||
sel2 = sel1;
|
||||
ctx.get_rewriter()(sel2);
|
||||
ctx.internalize(sel1, false);
|
||||
ctx.internalize(sel2, false);
|
||||
TRACE(array, tout << mk_bounded_pp(sel1, m) << "\n==\n" << mk_bounded_pp(sel2, m) << "\n";);
|
||||
return try_assign_eq(sel1, sel2);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
|
|
@ -881,6 +912,7 @@ namespace smt {
|
|||
st.update("array def as-array", m_stats.m_num_default_as_array_axiom);
|
||||
st.update("array sel as-array", m_stats.m_num_select_as_array_axiom);
|
||||
st.update("array def lambda", m_stats.m_num_default_lambda_axiom);
|
||||
st.update("array sel lambda", m_stats.m_num_select_lambda_axiom);
|
||||
st.update("array choice ax", m_stats.m_num_choice_axiom);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ namespace smt {
|
|||
bool instantiate_default_map_axiom(enode* map);
|
||||
bool instantiate_default_as_array_axiom(enode* arr);
|
||||
bool instantiate_default_lambda_def_axiom(enode* arr);
|
||||
bool instantiate_select_lambda_axiom(enode *lambda);
|
||||
|
||||
bool instantiate_choice_axiom(enode* ch);
|
||||
bool instantiate_parent_stores_default(theory_var v);
|
||||
|
||||
|
|
@ -96,6 +96,7 @@ namespace smt {
|
|||
bool instantiate_select_const_axiom(enode* select, enode* cnst);
|
||||
bool instantiate_select_as_array_axiom(enode* select, enode* arr);
|
||||
bool instantiate_select_map_axiom(enode* select, enode* map);
|
||||
bool instantiate_select_lambda_axiom(enode *select, enode *lambda);
|
||||
|
||||
bool instantiate_axiom_map_for(theory_var v);
|
||||
|
||||
|
|
|
|||
|
|
@ -437,11 +437,12 @@ namespace smt {
|
|||
return lit == arg;
|
||||
};
|
||||
auto lit1 = clause.get(0);
|
||||
[[maybe_unused]] auto lit2 = clause.get(1);
|
||||
auto position = 0;
|
||||
if (is_complement_to(is_true, lit1, e))
|
||||
position = 0;
|
||||
else {
|
||||
SASSERT(is_complement_to(is_true, clause.get(1), e));
|
||||
SASSERT(is_complement_to(is_true, lit2, e));
|
||||
position = 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4044,7 +4044,7 @@ public:
|
|||
if (!lp().is_feasible() || lp().has_changed_columns())
|
||||
make_feasible();
|
||||
vi = get_lpvar(v);
|
||||
auto st = lp().maximize_term(vi, term_max);
|
||||
auto st = lp().maximize_term(vi, term_max, /*fix_int_cols*/ true);
|
||||
if (has_int() && lp().has_inf_int()) {
|
||||
st = lp::lp_status::FEASIBLE;
|
||||
lp().restore_x();
|
||||
|
|
|
|||
|
|
@ -193,16 +193,16 @@ namespace smt {
|
|||
|
||||
void theory_nseq::new_eq_eh(theory_var v1, theory_var v2) {
|
||||
try {
|
||||
auto n1 = get_enode(v1);
|
||||
auto n2 = get_enode(v2);
|
||||
auto e1 = n1->get_expr();
|
||||
auto e2 = n2->get_expr();
|
||||
const auto n1 = get_enode(v1);
|
||||
const auto n2 = get_enode(v2);
|
||||
const auto e1 = n1->get_expr();
|
||||
const auto e2 = n2->get_expr();
|
||||
TRACE(seq, tout << mk_pp(e1, m) << " == " << mk_pp(e2, m) << "\n");
|
||||
//std::cout << mk_pp(e1, m) << " == " << mk_pp(e2, m) << std::endl;
|
||||
if (m_seq.is_re(e1)) {
|
||||
expr_ref s(m);
|
||||
auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_seq_in_re(r, s)) {
|
||||
zstring s;
|
||||
const auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_string_in_re(r, s)) {
|
||||
case l_false:
|
||||
// regexes are equivalent: nothing to do
|
||||
return;
|
||||
|
|
@ -237,15 +237,15 @@ namespace smt {
|
|||
}
|
||||
|
||||
void theory_nseq::new_diseq_eh(theory_var v1, theory_var v2) {
|
||||
auto n1 = get_enode(v1);
|
||||
auto n2 = get_enode(v2);
|
||||
auto e1 = n1->get_expr();
|
||||
auto e2 = n2->get_expr();
|
||||
const auto n1 = get_enode(v1);
|
||||
const auto n2 = get_enode(v2);
|
||||
const auto e1 = n1->get_expr();
|
||||
const auto e2 = n2->get_expr();
|
||||
TRACE(seq, tout << mk_pp(e1, m) << " != " << mk_pp(e2, m) << "\n");
|
||||
if (m_seq.is_re(e1)) {
|
||||
expr_ref s(m);
|
||||
auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_seq_in_re(r, s)) {
|
||||
zstring s;
|
||||
auto r = m_rewriter.mk_symmetric_diff(e1, e2);
|
||||
switch (m_rewriter.some_string_in_re(r, s)) {
|
||||
case l_false: {
|
||||
enode_pair_vector eqs;
|
||||
const auto lit = mk_eq(e1, e2, false);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ namespace smt {
|
|||
unsigned m_final_check_ls_steps = 30000;
|
||||
unsigned m_final_check_ls_steps_delta = 10000;
|
||||
unsigned m_final_check_ls_steps_min = 10000;
|
||||
unsigned m_final_check_ls_steps_max = 30000;
|
||||
bool m_has_unassigned_clause_after_resolve = false;
|
||||
unsigned m_after_resolve_decide_gap = 4;
|
||||
unsigned m_after_resolve_decide_count = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue