3
0
Fork 0
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:
CEisenhofer 2026-07-01 17:18:21 +02:00
commit 706f62286e
199 changed files with 11004 additions and 4584 deletions

View file

@ -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))

View file

@ -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";);

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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))

View file

@ -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);
};

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
};
};

View file

@ -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)) {

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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(); }
};

View file

@ -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});
}
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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();

View file

@ -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);

View file

@ -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;