3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-06-22 08:30:28 +00:00

Parallel tactic (#9824)

Co-authored-by: Ilana Shapiro <ilanashapiro@Ilanas-MacBook-Pro.local>
Co-authored-by: Ilana Shapiro <ilanashapiro@Ilanas-MBP.localdomain>
This commit is contained in:
Nikolaj Bjorner 2026-06-11 09:14:00 -07:00 committed by GitHub
parent 677abb589e
commit 6ef8660adb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1889 additions and 280 deletions

View file

@ -5,7 +5,7 @@ def_module_params('smt_parallel',
('inprocessing', BOOL, False, 'integrate in-processing as a heuristic simplification'),
('sls', BOOL, False, 'add sls-tactic as a separate worker thread outside the search tree parallelism'),
('num_global_bb_fl_threads', UINT, 0, 'run failed-literal backbone worker threads; default is 0 (off), supported values are 1 (negative mode only) or 2 (negative and positive mode)'),
('num_global_bb_batch_threads', UINT, 0, 'run Janota-style chunking backbone worker threads; default is 0 (off), supported values are 1 (negative mode only) or 2 (negative and positive mode)'),
('num_global_bb_batch_threads', UINT, 2, 'run Janota-style chunking backbone worker threads; default is 2 (negative and positive mode), supported values are 0 (off), 1 (negative mode only) or 2 (negative and positive mode)'),
('local_backbones', BOOL, False, 'enable local backbones experiment within the search tree parallelism'),
('core_minimize', BOOL, True, 'minimize unsat cores used for parallel cube backtracking'),
('ablate_backtracking', BOOL, False, 'ablation: pass entire cube as core instead of unsat core during backtracking'),

View file

@ -132,6 +132,8 @@ namespace sat {
m_best_phase.reset();
m_phase.reset();
m_prev_phase.reset();
m_phase_birthdate.reset();
m_best_phase_birthdate.reset();
m_assigned_since_gc.reset();
m_last_conflict.reset();
m_last_propagation.reset();
@ -161,6 +163,8 @@ namespace sat {
m_phase[v] = src.m_phase[v];
m_best_phase[v] = src.m_best_phase[v];
m_prev_phase[v] = src.m_prev_phase[v];
m_phase_birthdate[v] = src.m_phase_birthdate[v];
m_best_phase_birthdate[v] = src.m_best_phase_birthdate[v];
// inherit activity:
m_activity[v] = src.m_activity[v];
@ -267,6 +271,8 @@ namespace sat {
m_phase[v] = false;
m_best_phase[v] = false;
m_prev_phase[v] = false;
m_phase_birthdate[v] = 0;
m_best_phase_birthdate[v] = 0;
m_assigned_since_gc[v] = false;
m_last_conflict[v] = 0;
m_last_propagation[v] = 0;
@ -308,6 +314,8 @@ namespace sat {
m_phase.push_back(false);
m_best_phase.push_back(false);
m_prev_phase.push_back(false);
m_phase_birthdate.push_back(0);
m_best_phase_birthdate.push_back(0);
m_assigned_since_gc.push_back(false);
m_last_conflict.push_back(0);
m_last_propagation.push_back(0);
@ -645,6 +653,17 @@ namespace sat {
return 3*cls_allocator().get_allocation_size()/2 + memory::get_allocation_size() > memory::get_max_memory_size();
}
void solver::set_phase(literal l) {
if (l.var() >= num_vars())
return;
bool value = !l.sign();
if (m_phase[l.var()] != value)
m_phase_birthdate[l.var()] = m_stats.m_conflicts;
if (m_best_phase[l.var()] != value)
m_best_phase_birthdate[l.var()] = m_stats.m_conflicts;
m_best_phase[l.var()] = m_phase[l.var()] = value;
}
struct solver::cmp_activity {
solver& s;
cmp_activity(solver& s):s(s) {}
@ -896,6 +915,8 @@ namespace sat {
m_assignment[(~l).index()] = l_false;
bool_var v = l.var();
m_justification[v] = j;
if (m_phase[v] != !l.sign())
m_phase_birthdate[v] = m_stats.m_conflicts;
m_phase[v] = !l.sign();
m_assigned_since_gc[v] = true;
m_trail.push_back(l);
@ -904,17 +925,17 @@ namespace sat {
case BH_VSIDS:
break;
case BH_CHB:
m_last_propagation[v] = m_stats.m_conflict;
m_last_propagation[v] = m_stats.m_conflicts;
break;
}
if (m_config.m_anti_exploration) {
uint64_t age = m_stats.m_conflict - m_canceled[v];
uint64_t age = m_stats.m_conflicts - m_canceled[v];
if (age > 0) {
double decay = pow(0.95, static_cast<double>(age));
set_activity(v, static_cast<unsigned>(m_activity[v] * decay));
// NB. MapleSAT does not update canceled.
m_canceled[v] = m_stats.m_conflict;
m_canceled[v] = m_stats.m_conflicts;
}
}
@ -1378,8 +1399,12 @@ namespace sat {
lbool r = m_local_search->check(_lits.size(), _lits.data(), nullptr);
auto const& mdl = m_local_search->get_model();
if (mdl.size() == m_best_phase.size()) {
for (unsigned i = 0; i < m_best_phase.size(); ++i)
m_best_phase[i] = l_true == mdl[i];
for (unsigned i = 0; i < m_best_phase.size(); ++i) {
bool is_true = l_true == mdl[i];
if (m_best_phase[i] != is_true)
m_best_phase_birthdate[i] = m_stats.m_conflicts;
m_best_phase[i] = is_true;
}
if (r == l_true) {
m_conflicts_since_restart = 0;
@ -1671,12 +1696,12 @@ namespace sat {
while (!m_case_split_queue.empty()) {
if (m_config.m_anti_exploration) {
next = m_case_split_queue.min_var();
auto age = m_stats.m_conflict - m_canceled[next];
auto age = m_stats.m_conflicts - m_canceled[next];
while (age > 0) {
set_activity(next, static_cast<unsigned>(m_activity[next] * pow(0.95, static_cast<double>(age))));
m_canceled[next] = m_stats.m_conflict;
m_canceled[next] = m_stats.m_conflicts;
next = m_case_split_queue.min_var();
age = m_stats.m_conflict - m_canceled[next];
age = m_stats.m_conflicts - m_canceled[next];
}
}
next = m_case_split_queue.next_var();
@ -1714,6 +1739,32 @@ namespace sat {
}
}
uint64_t solver::get_preferred_phase_birthdate(bool_var v) const {
bool use_best =
m_config.m_phase == PS_FROZEN ||
((m_config.m_phase == PS_SAT_CACHING || m_config.m_phase == PS_LOCAL_SEARCH) && m_search_state == s_sat);
return use_best ? m_best_phase_birthdate[v] : m_phase_birthdate[v];
}
void solver::get_backbone_candidates(literal_vector& lits, unsigned max_num) {
struct candidate {
literal lit;
uint64_t age;
};
svector<candidate> cands;
uint64_t now = m_stats.m_conflicts;
for (bool_var v = 0; v < num_vars(); ++v) {
if (value(v) != l_undef || was_eliminated(v))
continue;
bool is_pos = guess(v);
cands.push_back({ literal(v, !is_pos), now - get_preferred_phase_birthdate(v) });
}
std::stable_sort(cands.begin(), cands.end(),
[](candidate const& a, candidate const& b) { return a.age > b.age; });
for (unsigned i = 0; i < cands.size() && i < max_num; ++i)
lits.push_back(cands[i].lit);
}
bool solver::decide() {
bool_var next;
lbool phase = l_undef;
@ -2145,8 +2196,13 @@ namespace sat {
for (bool_var v = 0; v < num; ++v) {
if (!was_eliminated(v)) {
m_model[v] = value(v);
m_phase[v] = value(v) == l_true;
m_best_phase[v] = value(v) == l_true;
bool is_true = value(v) == l_true;
if (m_phase[v] != is_true)
m_phase_birthdate[v] = m_stats.m_conflicts;
if (m_best_phase[v] != is_true)
m_best_phase_birthdate[v] = m_stats.m_conflicts;
m_phase[v] = is_true;
m_best_phase[v] = is_true;
}
}
TRACE(sat_mc_bug, m_mc.display(tout););
@ -2274,7 +2330,7 @@ namespace sat {
m_restart_logs++;
std::stringstream strm;
strm << "(sat.stats " << std::setw(6) << m_stats.m_conflict << " "
strm << "(sat.stats " << std::setw(6) << m_stats.m_conflicts << " "
<< std::setw(6) << m_stats.m_decision << " "
<< std::setw(4) << m_stats.m_restart
<< mk_stat(*this)
@ -2432,7 +2488,7 @@ namespace sat {
m_conflicts_since_init++;
m_conflicts_since_restart++;
m_conflicts_since_gc++;
m_stats.m_conflict++;
m_stats.m_conflicts++;
if (m_step_size > m_config.m_step_size_min)
m_step_size -= m_config.m_step_size_dec;
@ -2564,7 +2620,7 @@ namespace sat {
tout << "missed " << lit << "@" << lvl(lit) << "\n";);
CTRACE(sat, idx == 0, display(tout););
if (idx == 0)
IF_VERBOSE(0, verbose_stream() << "num-conflicts: " << m_stats.m_conflict << "\n");
IF_VERBOSE(0, verbose_stream() << "num-conflicts: " << m_stats.m_conflicts << "\n");
VERIFY(idx > 0);
idx--;
}
@ -2874,7 +2930,7 @@ namespace sat {
inc_activity(var);
break;
case BH_CHB:
m_last_conflict[var] = m_stats.m_conflict;
m_last_conflict[var] = m_stats.m_conflicts;
break;
default:
break;
@ -2915,13 +2971,18 @@ namespace sat {
for (unsigned i = head; i < sz; ++i) {
bool_var v = m_trail[i].var();
TRACE(forget_phase, tout << "forgetting phase of v" << v << "\n";);
m_phase[v] = m_rand() % 2 == 0;
bool value = m_rand() % 2 == 0;
if (m_phase[v] != value)
m_phase_birthdate[v] = m_stats.m_conflicts;
m_phase[v] = value;
}
if (is_sat_phase() && head >= m_best_phase_size) {
m_best_phase_size = head;
IF_VERBOSE(12, verbose_stream() << "sticky trail: " << head << "\n");
for (unsigned i = 0; i < head; ++i) {
bool_var v = m_trail[i].var();
if (m_best_phase[v] != m_phase[v])
m_best_phase_birthdate[v] = m_stats.m_conflicts;
m_best_phase[v] = m_phase[v];
}
set_has_new_best_phase(true);
@ -2971,23 +3032,43 @@ namespace sat {
void solver::do_rephase() {
switch (m_config.m_phase) {
case PS_ALWAYS_TRUE:
for (auto& p : m_phase) p = true;
for (unsigned i = 0; i < m_phase.size(); ++i) {
if (!m_phase[i])
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = true;
}
break;
case PS_ALWAYS_FALSE:
for (auto& p : m_phase) p = false;
for (unsigned i = 0; i < m_phase.size(); ++i) {
if (m_phase[i])
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = false;
}
break;
case PS_FROZEN:
break;
case PS_BASIC_CACHING:
switch (m_rephase.count % 4) {
case 0:
for (auto& p : m_phase) p = (m_rand() % 2) == 0;
for (unsigned i = 0; i < m_phase.size(); ++i) {
bool value = (m_rand() % 2) == 0;
if (m_phase[i] != value)
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = value;
}
break;
case 1:
for (auto& p : m_phase) p = false;
for (unsigned i = 0; i < m_phase.size(); ++i) {
if (m_phase[i])
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = false;
}
break;
case 2:
for (auto& p : m_phase) p = !p;
for (unsigned i = 0; i < m_phase.size(); ++i) {
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = !m_phase[i];
}
break;
default:
break;
@ -2995,18 +3076,29 @@ namespace sat {
break;
case PS_SAT_CACHING:
if (m_search_state == s_sat)
for (unsigned i = 0; i < m_phase.size(); ++i)
m_phase[i] = m_best_phase[i];
for (unsigned i = 0; i < m_phase.size(); ++i) {
if (m_phase[i] != m_best_phase[i])
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = m_best_phase[i];
}
break;
case PS_RANDOM:
for (auto& p : m_phase) p = (m_rand() % 2) == 0;
for (unsigned i = 0; i < m_phase.size(); ++i) {
bool value = (m_rand() % 2) == 0;
if (m_phase[i] != value)
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = value;
}
break;
case PS_LOCAL_SEARCH:
if (m_search_state == s_sat) {
if (m_rand() % 2 == 0)
bounded_local_search();
for (unsigned i = 0; i < m_phase.size(); ++i)
m_phase[i] = m_best_phase[i];
for (unsigned i = 0; i < m_phase.size(); ++i) {
if (m_phase[i] != m_best_phase[i])
m_phase_birthdate[i] = m_stats.m_conflicts;
m_phase[i] = m_best_phase[i];
}
}
break;
@ -3601,6 +3693,8 @@ namespace sat {
m_phase.shrink(v);
m_best_phase.shrink(v);
m_prev_phase.shrink(v);
m_phase_birthdate.shrink(v);
m_best_phase_birthdate.shrink(v);
m_assigned_since_gc.shrink(v);
m_simplifier.reset_todos();
}
@ -3644,7 +3738,7 @@ namespace sat {
SASSERT(value(v) == l_undef);
m_case_split_queue.unassign_var_eh(v);
if (m_config.m_anti_exploration) {
m_canceled[v] = m_stats.m_conflict;
m_canceled[v] = m_stats.m_conflicts;
}
}
m_trail.shrink(old_sz);
@ -3812,7 +3906,7 @@ namespace sat {
double multiplier = m_config.m_reward_offset * (is_sat ? m_config.m_reward_multiplier : 1.0);
for (unsigned i = qhead; i < m_trail.size(); ++i) {
auto v = m_trail[i].var();
auto d = m_stats.m_conflict - m_last_conflict[v] + 1;
auto d = m_stats.m_conflicts - m_last_conflict[v] + 1;
if (d == 0) d = 1;
auto reward = multiplier / d;
auto activity = m_activity[v];
@ -4745,7 +4839,7 @@ namespace sat {
st.update("sat mk var", m_mk_var);
st.update("sat gc clause", m_gc_clause);
st.update("sat del clause", m_del_clause);
st.update("sat conflicts", m_conflict);
st.update("sat conflicts", m_conflicts);
st.update("sat decisions", m_decision);
st.update("sat propagations 2ary", m_bin_propagate);
st.update("sat propagations 3ary", m_ter_propagate);

View file

@ -60,7 +60,7 @@ namespace sat {
unsigned m_mk_bin_clause;
unsigned m_mk_ter_clause;
unsigned m_mk_clause;
unsigned m_conflict;
unsigned m_conflicts;
unsigned m_propagate;
unsigned m_bin_propagate;
unsigned m_ter_propagate;
@ -148,6 +148,8 @@ namespace sat {
bool_vector m_phase;
bool_vector m_best_phase;
bool_vector m_prev_phase;
svector<uint64_t> m_phase_birthdate;
svector<uint64_t> m_best_phase_birthdate;
bool m_new_best_phase = false;
svector<char> m_assigned_since_gc;
search_state m_search_state;
@ -373,12 +375,17 @@ namespace sat {
bool was_eliminated(bool_var v) const { return m_eliminated[v]; }
void set_eliminated(bool_var v, bool f) override;
bool was_eliminated(literal l) const { return was_eliminated(l.var()); }
void set_phase(literal l) override { if (l.var() < num_vars()) m_best_phase[l.var()] = m_phase[l.var()] = !l.sign(); }
void set_phase(literal l) override;
bool get_phase(bool_var b) { return m_phase.get(b, false); }
bool get_best_phase(bool_var b) { return m_best_phase.get(b, false); }
uint64_t get_phase_birthdate(bool_var b) const { return m_phase_birthdate.get(b, 0); }
uint64_t get_best_phase_birthdate(bool_var b) const { return m_best_phase_birthdate.get(b, 0); }
uint64_t get_preferred_phase_birthdate(bool_var b) const;
void set_has_new_best_phase(bool b) { m_new_best_phase = b; }
bool has_new_best_phase() const { return m_new_best_phase; }
void move_to_front(bool_var b);
unsigned get_activity(bool_var v) const { return m_activity[v]; }
void get_backbone_candidates(literal_vector& lits, unsigned max_num);
unsigned scope_lvl() const { return m_scope_lvl; }
unsigned search_lvl() const { return m_search_lvl; }
bool at_search_lvl() const { return m_scope_lvl == m_search_lvl; }

View file

@ -405,6 +405,50 @@ public:
}
}
unsigned get_assign_level(expr* e) const override {
expr* atom = e;
m.is_not(e, atom);
sat::bool_var bv = m_map.to_bool_var(atom);
return bv == sat::null_bool_var ? UINT_MAX : m_solver.lvl(bv);
}
bool is_relevant(expr* e) const override {
expr* atom = e;
m.is_not(e, atom);
sat::bool_var bv = m_map.to_bool_var(atom);
if (bv == sat::null_bool_var)
return true;
auto* ext = dynamic_cast<euf::solver*>(m_solver.get_extension());
return !ext || ext->is_relevant(bv);
}
unsigned get_num_bool_vars() const override {
return m_solver.num_vars();
}
unsigned get_bool_var(expr* e) const override {
expr* atom = e;
m.is_not(e, atom);
sat::bool_var bv = m_map.to_bool_var(atom);
return bv == sat::null_bool_var ? UINT_MAX : bv;
}
expr* bool_var2expr(unsigned v) const override {
return v < m_solver.num_vars() ? m_map.bool_var2expr(v) : nullptr;
}
lbool get_assignment(unsigned v) const override {
return v < m_solver.num_vars() ? m_solver.value(v) : l_undef;
}
double get_activity(unsigned v) const override {
return v < m_solver.num_vars() ? static_cast<double>(m_solver.get_activity(v)) : 0.0;
}
bool was_eliminated(unsigned v) const override {
return v < m_solver.num_vars() && m_solver.was_eliminated(v);
}
expr_ref_vector get_trail(unsigned max_level) override {
expr_ref_vector result(m);
unsigned sz = m_solver.trail_size();
@ -451,8 +495,35 @@ public:
vars.push_back(kv.m_value);
}
sat::literal_vector lits;
lbool result = m_solver.cube(vars, lits, backtrack_level);
expr_ref_vector fmls(m);
if (!m_params.get_bool("cube.lookahead", false)) {
sat::bool_var best = sat::null_bool_var;
double best_activity = 0.0;
unsigned n = 0;
for (sat::bool_var v : vars) {
if (get_assignment(v) != l_undef || was_eliminated(v))
continue;
double activity = get_activity(v);
if (best == sat::null_bool_var || activity > best_activity || (activity == best_activity && m_solver.rand()(++n) == 0)) {
best = v;
best_activity = activity;
}
}
if (best == sat::null_bool_var)
return expr_ref_vector(m);
expr* e = bool_var2expr(best);
SASSERT(e);
if (e)
fmls.push_back(e);
vs.reset();
for (sat::bool_var v : vars) {
expr* x = bool_var2expr(v);
if (x)
vs.push_back(x);
}
return fmls;
}
lbool result = m_solver.cube(vars, lits, backtrack_level);
expr_ref_vector lit2expr(m);
lit2expr.resize(m_solver.num_vars() * 2);
m_map.mk_inv(lit2expr);
@ -482,6 +553,27 @@ public:
return fmls;
}
void get_backbone_candidates(vector<solver::scored_literal>& candidates, unsigned max_num) override {
if (!is_internalized()) {
lbool r = internalize_formulas();
if (r != l_true)
return;
}
convert_internalized();
sat::literal_vector lits;
m_solver.get_backbone_candidates(lits, max_num);
expr_ref_vector lit2expr(m);
lit2expr.resize(m_solver.num_vars() * 2);
m_map.mk_inv(lit2expr);
uint64_t now = m_solver.get_stats().m_conflicts;
for (sat::literal lit : lits) {
expr* e = lit2expr.get(lit.index());
if (!e)
continue;
candidates.push_back(scored_literal(m, e, static_cast<double>(now - m_solver.get_phase_birthdate(lit.var()))));
}
}
expr* congruence_next(expr* e) override { return e; }
expr* congruence_root(expr* e) override { return e; }
expr_ref congruence_explain(expr* a, expr* b) override { return expr_ref(m.mk_eq(a, b), m); }

View file

@ -49,6 +49,13 @@ sat::bool_var atom2bool_var::to_bool_var(expr * n) const {
return m_mapping[idx].m_value;
}
expr* atom2bool_var::bool_var2expr(sat::bool_var v) const {
for (auto const& kv : m_mapping)
if (kv.m_value == v)
return kv.m_key;
return nullptr;
}
struct collect_boolean_interface_proc {
struct visitor {
obj_hashtable<expr> & m_r;

View file

@ -29,6 +29,7 @@ public:
atom2bool_var(ast_manager & m):expr2var(m) {}
void insert(expr * n, sat::bool_var v) { expr2var::insert(n, v); }
sat::bool_var to_bool_var(expr * n) const;
expr* bool_var2expr(sat::bool_var v) const;
void mk_inv(expr_ref_vector & lit2expr) const;
void mk_var_inv(expr_ref_vector & var2expr) const;
// return true if the mapping contains uninterpreted atoms.

View file

@ -155,7 +155,7 @@ namespace bv {
void ackerman::propagate() {
auto* n = m_queue;
vv* k = nullptr;
unsigned num_prop = static_cast<unsigned>(s.s().get_stats().m_conflict * s.get_config().m_dack_factor);
unsigned num_prop = static_cast<unsigned>(s.s().get_stats().m_conflicts * s.get_config().m_dack_factor);
num_prop = std::min(num_prop, m_table.size());
for (unsigned i = 0; i < num_prop; ++i, n = k) {
k = n->next();

View file

@ -171,7 +171,7 @@ namespace euf {
SASSERT(ctx.s().at_base_lvl());
auto* n = m_queue;
inference* k = nullptr;
unsigned num_prop = static_cast<unsigned>(ctx.s().get_stats().m_conflict * ctx.m_config.m_dack_factor);
unsigned num_prop = static_cast<unsigned>(ctx.s().get_stats().m_conflicts * ctx.m_config.m_dack_factor);
num_prop = std::min(num_prop, m_table.size());
for (unsigned i = 0; i < num_prop; ++i, n = k) {
k = n->next();

View file

@ -3652,6 +3652,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;

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;
@ -452,6 +454,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; }
@ -538,6 +542,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;
}
@ -1697,6 +1703,8 @@ namespace smt {
lbool setup_and_check(bool reset_cancel = true);
void setup_for_parallel();
void reduce_assertions();
bool resource_limits_exceeded();
@ -1914,4 +1922,3 @@ namespace smt {
};

View file

@ -280,6 +280,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;
}
void kernel::reset_aux_statistics() {
m_imp->m_kernel.m_aux_stats.reset();
}
void kernel::add_aux_statistics(::statistics const& st) {
m_imp->m_kernel.m_aux_stats.copy(st);
}
context & kernel::get_context() {
return m_imp->m_kernel;
}

View file

@ -300,6 +300,14 @@ namespace smt {
*/
static void collect_param_descrs(param_descrs & d);
void pop_to_base_level();
void set_preprocess(bool f);
void reset_aux_statistics();
void add_aux_statistics(::statistics const& st);
void register_on_clause(void* ctx, user_propagator::on_clause_eh_t& on_clause);
/**

View file

@ -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() {
@ -887,6 +887,7 @@ 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;

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

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;
@ -86,14 +90,17 @@ namespace {
solver * translate(ast_manager & m, params_ref const & p) override {
ast_translation translator(get_manager(), m);
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, m, 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,112 @@ namespace {
return m_context.get_trail(max_level);
}
expr_ref_vector get_assigned_literals() override {
expr_ref_vector result(m);
auto& ctx = const_cast<smt::kernel&>(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& ctx = const_cast<smt::kernel&>(m_context).get_context();
expr* atom = e;
get_manager().is_not(e, atom);
if (!ctx.b_internalized(atom))
return UINT_MAX;
return ctx.get_assign_level(ctx.get_bool_var(atom));
}
bool is_relevant(expr* e) const override {
auto& ctx = const_cast<smt::kernel&>(m_context).get_context();
expr* atom = e;
get_manager().is_not(e, atom);
return ctx.b_internalized(atom) && ctx.is_relevant(atom);
}
unsigned get_num_bool_vars() const override {
return const_cast<smt::kernel&>(m_context).get_context().get_num_bool_vars();
}
unsigned get_bool_var(expr* e) const override {
auto& ctx = const_cast<smt::kernel&>(m_context).get_context();
expr* atom = e;
get_manager().is_not(e, atom);
return ctx.b_internalized(atom) ? ctx.get_bool_var(atom) : UINT_MAX;
}
void pop_to_base_level() override {
m_context.pop_to_base_level();
}
void setup_for_parallel() override {
const_cast<smt::kernel&>(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 = const_cast<smt::kernel&>(m_context).get_context();
ctx.get_fparams().m_max_conflicts = max_conflicts;
}
unsigned get_max_conflicts() const override {
return const_cast<smt::kernel&>(m_context).get_context().get_fparams().m_max_conflicts;
}
void reset_parallel_statistics() override {
m_context.reset_aux_statistics();
}
void add_parallel_statistics(statistics const& st) override {
m_context.add_aux_statistics(st);
}
void collect_parallel_statistics(statistics& st) const override {
m_context.collect_statistics(st);
}
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);
}
@ -347,6 +460,44 @@ namespace {
expr_ref_vector cube(expr_ref_vector& vars, unsigned cutoff) override {
ast_manager& m = get_manager();
if (!get_params().get_bool("cube.lookahead", false)) {
auto& ctx = m_context.get_context();
obj_hashtable<expr> selected_vars;
for (expr* v : vars)
selected_vars.insert(v);
expr_ref_vector candidates(m);
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;
if (!selected_vars.empty() && !selected_vars.contains(e))
continue;
candidates.push_back(e);
double new_score = ctx.get_activity(v);
// Match smt_parallel: cube split tie-breaking is independent
// of the per-worker SMT search seed.
if (new_score > score || !result || (new_score == score && m_rand(++n) == 0)) {
score = new_score;
result = e;
}
}
vars.reset();
vars.append(candidates);
expr_ref_vector lits(m);
if (result)
lits.push_back(result);
return lits;
}
if (!m_cuber) {
m_cuber = alloc(cuber, *this);
// force propagation

View file

@ -6,6 +6,9 @@ def_module_params('parallel',
('enable', BOOL, False, 'enable parallel solver by default on selected tactics (for QF_BV)'),
('enable2', BOOL, False, 'enable (experimental) parallel solver by default on selected tactics (for QF_BV)'),
('threads.max', UINT, 10000, 'caps maximal number of threads below the number of processors'),
('num_bb_threads', UINT, 2, 'run Janota-style chunking backbone worker threads; default is 2 (negative and positive mode), supported values are 0 (off), 1 (negative mode only) or 2 (negative and positive mode)'),
('core_minimize', BOOL, True, 'minimize unsat cores used for parallel cube backtracking'),
('cube.lookahead', BOOL, False, 'use lookahead cubing in the parallel solver; when false, use VSIDS activity to select one split literal'),
('conquer.batch_size', UINT, 100, 'number of cubes to batch together for fast conquer'),
('conquer.restart.max', UINT, 5, 'maximal number of restarts during conquer phase'),
('conquer.delay', UINT, 10, 'delay of cubes until applying conquer'),

File diff suppressed because it is too large Load diff

View file

@ -57,7 +57,15 @@ solver* mk_smt2_solver(ast_manager& m, params_ref const& p, symbol const& logic
class solver : public check_sat_result, public user_propagator::core {
params_ref m_params;
symbol m_cancel_backup_file;
statistics m_parallel_stats;
public:
struct scored_literal {
expr_ref lit;
double score = 0.0;
scored_literal(ast_manager& m, expr* e, double s): lit(e, m), score(s) {}
scored_literal(expr_ref const& e, double s): lit(e), score(s) {}
};
solver(ast_manager& m): check_sat_result(m) {}
/**
@ -298,9 +306,47 @@ public:
expr_ref_vector get_non_units();
virtual expr_ref_vector get_trail(unsigned max_level) = 0; // { return expr_ref_vector(get_manager()); }
virtual expr_ref_vector get_assigned_literals() { return get_trail(UINT_MAX); }
virtual unsigned get_assign_level(expr* e) const { return UINT_MAX; }
virtual bool is_relevant(expr* e) const { return true; }
virtual unsigned get_num_bool_vars() const { return UINT_MAX; }
virtual unsigned get_bool_var(expr* e) const { return UINT_MAX; }
virtual expr* bool_var2expr(unsigned) const { return nullptr; }
virtual lbool get_assignment(unsigned) const { return l_undef; }
virtual double get_activity(unsigned) const { return 0.0; }
virtual bool was_eliminated(unsigned) const { return false; }
virtual void pop_to_base_level() {}
virtual void setup_for_parallel() {}
virtual void set_preprocess(bool) {}
virtual void set_max_conflicts(unsigned max_conflicts) {
params_ref p;
p.set_uint("max_conflicts", max_conflicts);
updt_params(p);
}
virtual unsigned get_max_conflicts() const { return UINT_MAX; }
virtual void reset_parallel_statistics() {
m_parallel_stats.reset();
}
virtual void add_parallel_statistics(statistics const& st) {
m_parallel_stats.copy(st);
}
virtual void collect_parallel_statistics(statistics& st) const {
st.copy(m_parallel_stats);
collect_statistics(st);
}
virtual void get_levels(ptr_vector<expr> const& vars, unsigned_vector& depth) = 0;
virtual void get_backbone_candidates(vector<scored_literal>&, unsigned) {}
class scoped_push {
solver& s;
bool m_nopop;

View file

@ -127,6 +127,10 @@ public:
m_base->get_levels(vars, depth);
}
void get_backbone_candidates(vector<solver::scored_literal>& candidates, unsigned max_num) override {
m_base->get_backbone_candidates(candidates, max_num);
}
expr_ref_vector get_trail(unsigned max_level) override {
return m_base->get_trail(max_level);
}

View file

@ -68,9 +68,17 @@ namespace search_tree {
literal const &get_literal() const {
return m_literal;
}
bool path_contains_atom(literal const& l) const {
for (node const* n = this; n; n = n->parent())
if (!Config::literal_is_null(n->get_literal()) && Config::same_atom(n->get_literal(), l))
return true;
return false;
}
void split(literal const &a, literal const &b) {
SASSERT(!Config::literal_is_null(a));
SASSERT(!Config::literal_is_null(b));
VERIFY(!path_contains_atom(a));
VERIFY(!path_contains_atom(b));
if (m_status != status::active)
return;
SASSERT(!m_left);